blob: 90b50613f5250be0a674a651de3a17418da181af [file] [log] [blame]
import os
import pytest
import shutil
import subprocess
from ruamel.yaml.comments import CommentedSet
from tests.testutils import cli, create_repo, ALL_REPO_KINDS
from buildstream import _yaml
from buildstream._exceptions import ErrorDomain, LoadError, LoadErrorReason
from buildstream._workspaces import BST_WORKSPACE_FORMAT_VERSION
repo_kinds = [(kind) for kind in ALL_REPO_KINDS]
# Project directory
DATA_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"project",
)
def open_workspace(cli, tmpdir, datafiles, kind, track, suffix=''):
project = os.path.join(datafiles.dirname, datafiles.basename)
bin_files_path = os.path.join(project, 'files', 'bin-files')
element_path = os.path.join(project, 'elements')
element_name = 'workspace-test-{}{}.bst'.format(kind, suffix)
workspace = os.path.join(str(tmpdir), 'workspace{}'.format(suffix))
# Create our repo object of the given source type with
# the bin files, and then collect the initial ref.
#
repo = create_repo(kind, str(tmpdir))
ref = repo.create(bin_files_path)
if track:
ref = None
# Write out our test target
element = {
'kind': 'import',
'sources': [
repo.source_config(ref=ref)
]
}
_yaml.dump(element,
os.path.join(element_path,
element_name))
# Assert that there is no reference, a track & fetch is needed
state = cli.get_element_state(project, element_name)
if track:
assert state == 'no reference'
else:
assert state == 'fetch needed'
# Now open the workspace, this should have the effect of automatically
# tracking & fetching the source from the repo.
args = ['workspace', 'open']
if track:
args.append('--track')
args.extend([element_name, workspace])
result = cli.run(project=project, args=args)
result.assert_success()
# Assert that we are now buildable because the source is
# now cached.
assert cli.get_element_state(project, element_name) == 'buildable'
# Check that the executable hello file is found in the workspace
filename = os.path.join(workspace, 'usr', 'bin', 'hello')
assert os.path.exists(filename)
return (element_name, project, workspace)
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("kind", repo_kinds)
def test_open(cli, tmpdir, datafiles, kind):
open_workspace(cli, tmpdir, datafiles, kind, False)
@pytest.mark.datafiles(DATA_DIR)
def test_open_bzr_customize(cli, tmpdir, datafiles):
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, "bzr", False)
# Check that the .bzr dir exists
bzrdir = os.path.join(workspace, ".bzr")
assert(os.path.isdir(bzrdir))
# Check that the correct origin branch is set
element_config = _yaml.load(os.path.join(project, "elements", element_name))
source_config = element_config['sources'][0]
output = subprocess.check_output(["bzr", "info"], cwd=workspace)
stripped_url = source_config['url'].lstrip("file:///")
expected_output_str = ("checkout of branch: /{}/{}"
.format(stripped_url, source_config['track']))
assert(expected_output_str in str(output))
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("kind", repo_kinds)
def test_open_track(cli, tmpdir, datafiles, kind):
open_workspace(cli, tmpdir, datafiles, kind, True)
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("kind", repo_kinds)
def test_open_force(cli, tmpdir, datafiles, kind):
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, kind, False)
# Close the workspace
result = cli.run(project=project, args=[
'workspace', 'close', element_name
])
result.assert_success()
# Assert the workspace dir still exists
assert os.path.exists(workspace)
# Now open the workspace again with --force, this should happily succeed
result = cli.run(project=project, args=[
'workspace', 'open', '--force', element_name, workspace
])
result.assert_success()
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("kind", repo_kinds)
def test_close(cli, tmpdir, datafiles, kind):
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, kind, False)
# Close the workspace
result = cli.run(project=project, args=[
'workspace', 'close', '--remove-dir', element_name
])
result.assert_success()
# Assert the workspace dir has been deleted
assert not os.path.exists(workspace)
@pytest.mark.datafiles(DATA_DIR)
def test_close_removed(cli, tmpdir, datafiles):
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, 'git', False)
# Remove it first, closing the workspace should work
shutil.rmtree(workspace)
# Close the workspace
result = cli.run(project=project, args=[
'workspace', 'close', element_name
])
result.assert_success()
# Assert the workspace dir has been deleted
assert not os.path.exists(workspace)
@pytest.mark.datafiles(DATA_DIR)
def test_close_nonexistant_element(cli, tmpdir, datafiles):
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, 'git', False)
element_path = os.path.join(datafiles.dirname, datafiles.basename, 'elements', element_name)
# First brutally remove the element.bst file, ensuring that
# the element does not exist anymore in the project where
# we want to close the workspace.
os.remove(element_path)
# Close the workspace
result = cli.run(project=project, args=[
'workspace', 'close', '--remove-dir', element_name
])
result.assert_success()
# Assert the workspace dir has been deleted
assert not os.path.exists(workspace)
@pytest.mark.datafiles(DATA_DIR)
def test_close_multiple(cli, tmpdir, datafiles):
tmpdir_alpha = os.path.join(str(tmpdir), 'alpha')
tmpdir_beta = os.path.join(str(tmpdir), 'beta')
alpha, project, workspace_alpha = open_workspace(
cli, tmpdir_alpha, datafiles, 'git', False, suffix='-alpha')
beta, project, workspace_beta = open_workspace(
cli, tmpdir_beta, datafiles, 'git', False, suffix='-beta')
# Close the workspaces
result = cli.run(project=project, args=[
'workspace', 'close', '--remove-dir', alpha, beta
])
result.assert_success()
# Assert the workspace dirs have been deleted
assert not os.path.exists(workspace_alpha)
assert not os.path.exists(workspace_beta)
@pytest.mark.datafiles(DATA_DIR)
def test_close_all(cli, tmpdir, datafiles):
tmpdir_alpha = os.path.join(str(tmpdir), 'alpha')
tmpdir_beta = os.path.join(str(tmpdir), 'beta')
alpha, project, workspace_alpha = open_workspace(
cli, tmpdir_alpha, datafiles, 'git', False, suffix='-alpha')
beta, project, workspace_beta = open_workspace(
cli, tmpdir_beta, datafiles, 'git', False, suffix='-beta')
# Close the workspaces
result = cli.run(project=project, args=[
'workspace', 'close', '--remove-dir', '--all'
])
result.assert_success()
# Assert the workspace dirs have been deleted
assert not os.path.exists(workspace_alpha)
assert not os.path.exists(workspace_beta)
@pytest.mark.datafiles(DATA_DIR)
def test_reset(cli, tmpdir, datafiles):
# Open the workspace
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, 'git', False)
# Modify workspace
shutil.rmtree(os.path.join(workspace, 'usr', 'bin'))
os.makedirs(os.path.join(workspace, 'etc'))
with open(os.path.join(workspace, 'etc', 'pony.conf'), 'w') as f:
f.write("PONY='pink'")
# Now reset the open workspace, this should have the
# effect of reverting our changes.
result = cli.run(project=project, args=[
'workspace', 'reset', element_name
])
result.assert_success()
assert os.path.exists(os.path.join(workspace, 'usr', 'bin', 'hello'))
assert not os.path.exists(os.path.join(workspace, 'etc', 'pony.conf'))
@pytest.mark.datafiles(DATA_DIR)
def test_reset_multiple(cli, tmpdir, datafiles):
# Open the workspaces
tmpdir_alpha = os.path.join(str(tmpdir), 'alpha')
tmpdir_beta = os.path.join(str(tmpdir), 'beta')
alpha, project, workspace_alpha = open_workspace(
cli, tmpdir_alpha, datafiles, 'git', False, suffix='-alpha')
beta, project, workspace_beta = open_workspace(
cli, tmpdir_beta, datafiles, 'git', False, suffix='-beta')
# Modify workspaces
shutil.rmtree(os.path.join(workspace_alpha, 'usr', 'bin'))
os.makedirs(os.path.join(workspace_beta, 'etc'))
with open(os.path.join(workspace_beta, 'etc', 'pony.conf'), 'w') as f:
f.write("PONY='pink'")
# Now reset the open workspaces, this should have the
# effect of reverting our changes.
result = cli.run(project=project, args=[
'workspace', 'reset', alpha, beta,
])
result.assert_success()
assert os.path.exists(os.path.join(workspace_alpha, 'usr', 'bin', 'hello'))
assert not os.path.exists(os.path.join(workspace_beta, 'etc', 'pony.conf'))
@pytest.mark.datafiles(DATA_DIR)
def test_reset_all(cli, tmpdir, datafiles):
# Open the workspaces
tmpdir_alpha = os.path.join(str(tmpdir), 'alpha')
tmpdir_beta = os.path.join(str(tmpdir), 'beta')
alpha, project, workspace_alpha = open_workspace(
cli, tmpdir_alpha, datafiles, 'git', False, suffix='-alpha')
beta, project, workspace_beta = open_workspace(
cli, tmpdir_beta, datafiles, 'git', False, suffix='-beta')
# Modify workspaces
shutil.rmtree(os.path.join(workspace_alpha, 'usr', 'bin'))
os.makedirs(os.path.join(workspace_beta, 'etc'))
with open(os.path.join(workspace_beta, 'etc', 'pony.conf'), 'w') as f:
f.write("PONY='pink'")
# Now reset the open workspace, this should have the
# effect of reverting our changes.
result = cli.run(project=project, args=[
'workspace', 'reset', '--all'
])
result.assert_success()
assert os.path.exists(os.path.join(workspace_alpha, 'usr', 'bin', 'hello'))
assert not os.path.exists(os.path.join(workspace_beta, 'etc', 'pony.conf'))
@pytest.mark.datafiles(DATA_DIR)
def test_list(cli, tmpdir, datafiles):
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, 'git', False)
# Now list the workspaces
result = cli.run(project=project, args=[
'workspace', 'list'
])
result.assert_success()
loaded = _yaml.load_data(result.output)
assert isinstance(loaded.get('workspaces'), list)
workspaces = loaded['workspaces']
assert len(workspaces) == 1
space = workspaces[0]
assert space['element'] == element_name
assert space['directory'] == workspace
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("kind", repo_kinds)
@pytest.mark.parametrize("strict", [("strict"), ("non-strict")])
def test_build(cli, tmpdir, datafiles, kind, strict):
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, kind, False)
checkout = os.path.join(str(tmpdir), 'checkout')
# Modify workspace
shutil.rmtree(os.path.join(workspace, 'usr', 'bin'))
os.makedirs(os.path.join(workspace, 'etc'))
with open(os.path.join(workspace, 'etc', 'pony.conf'), 'w') as f:
f.write("PONY='pink'")
# Configure strict mode
strict_mode = True
if strict != 'strict':
strict_mode = False
cli.configure({
'projects': {
'test': {
'strict': strict_mode
}
}
})
# Build modified workspace
assert cli.get_element_state(project, element_name) == 'buildable'
assert cli.get_element_key(project, element_name) == "{:?<64}".format('')
result = cli.run(project=project, args=['build', element_name])
result.assert_success()
assert cli.get_element_state(project, element_name) == 'cached'
assert cli.get_element_key(project, element_name) != "{:?<64}".format('')
# Checkout the result
result = cli.run(project=project, args=[
'checkout', element_name, checkout
])
result.assert_success()
# Check that the pony.conf from the modified workspace exists
filename = os.path.join(checkout, 'etc', 'pony.conf')
assert os.path.exists(filename)
# Check that the original /usr/bin/hello is not in the checkout
assert not os.path.exists(os.path.join(checkout, 'usr', 'bin', 'hello'))
@pytest.mark.datafiles(DATA_DIR)
def test_buildable_no_ref(cli, tmpdir, datafiles):
project = os.path.join(datafiles.dirname, datafiles.basename)
element_name = 'workspace-test-no-ref.bst'
element_path = os.path.join(project, 'elements')
# Write out our test target without any source ref
repo = create_repo('git', str(tmpdir))
element = {
'kind': 'import',
'sources': [
repo.source_config()
]
}
_yaml.dump(element,
os.path.join(element_path,
element_name))
# Assert that this target is not buildable when no workspace is associated.
assert cli.get_element_state(project, element_name) == 'no reference'
# Now open the workspace. We don't need to checkout the source though.
workspace = os.path.join(str(tmpdir), 'workspace-no-ref')
os.makedirs(workspace)
args = ['workspace', 'open', '--no-checkout', element_name, workspace]
result = cli.run(project=project, args=args)
result.assert_success()
# Assert that the target is now buildable.
assert cli.get_element_state(project, element_name) == 'buildable'
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("modification", [("addfile"), ("removefile"), ("modifyfile")])
@pytest.mark.parametrize("strict", [("strict"), ("non-strict")])
def test_detect_modifications(cli, tmpdir, datafiles, modification, strict):
element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, 'git', False)
checkout = os.path.join(str(tmpdir), 'checkout')
# Configure strict mode
strict_mode = True
if strict != 'strict':
strict_mode = False
cli.configure({
'projects': {
'test': {
'strict': strict_mode
}
}
})
# Build clean workspace
assert cli.get_element_state(project, element_name) == 'buildable'
assert cli.get_element_key(project, element_name) == "{:?<64}".format('')
result = cli.run(project=project, args=['build', element_name])
result.assert_success()
assert cli.get_element_state(project, element_name) == 'cached'
assert cli.get_element_key(project, element_name) != "{:?<64}".format('')
# Modify the workspace in various different ways, ensuring we
# properly detect the changes.
#
if modification == 'addfile':
os.makedirs(os.path.join(workspace, 'etc'))
with open(os.path.join(workspace, 'etc', 'pony.conf'), 'w') as f:
f.write("PONY='pink'")
elif modification == 'removefile':
os.remove(os.path.join(workspace, 'usr', 'bin', 'hello'))
elif modification == 'modifyfile':
with open(os.path.join(workspace, 'usr', 'bin', 'hello'), 'w') as f:
f.write('cookie')
else:
# This cannot be reached
assert 0
# First assert that the state is properly detected
assert cli.get_element_state(project, element_name) == 'buildable'
assert cli.get_element_key(project, element_name) == "{:?<64}".format('')
# Since there are different things going on at `bst build` time
# than `bst show` time, we also want to build / checkout again,
# and ensure that the result contains what we expect.
result = cli.run(project=project, args=['build', element_name])
result.assert_success()
assert cli.get_element_state(project, element_name) == 'cached'
assert cli.get_element_key(project, element_name) != "{:?<64}".format('')
# Checkout the result
result = cli.run(project=project, args=[
'checkout', element_name, checkout
])
result.assert_success()
# Check the result for the changes we made
#
if modification == 'addfile':
filename = os.path.join(checkout, 'etc', 'pony.conf')
assert os.path.exists(filename)
elif modification == 'removefile':
assert not os.path.exists(os.path.join(checkout, 'usr', 'bin', 'hello'))
elif modification == 'modifyfile':
with open(os.path.join(workspace, 'usr', 'bin', 'hello'), 'r') as f:
data = f.read()
assert data == 'cookie'
else:
# This cannot be reached
assert 0
# Ensure that various versions that should not be accepted raise a
# LoadError.INVALID_DATA
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("workspace_cfg", [
# Test loading a negative workspace version
{"format-version": -1},
# Test loading version 0 with two sources
{
"format-version": 0,
"alpha.bst": {
0: "/workspaces/bravo",
1: "/workspaces/charlie",
}
},
# Test loading a version with decimals
{"format-version": 0.5},
# Test loading a future version
{"format-version": BST_WORKSPACE_FORMAT_VERSION + 1}
])
def test_list_unsupported_workspace(cli, tmpdir, datafiles, workspace_cfg):
project = os.path.join(datafiles.dirname, datafiles.basename)
bin_files_path = os.path.join(project, 'files', 'bin-files')
element_path = os.path.join(project, 'elements')
element_name = 'workspace-version.bst'
os.makedirs(os.path.join(project, '.bst'))
workspace_config_path = os.path.join(project, '.bst', 'workspaces.yml')
_yaml.dump(workspace_cfg, workspace_config_path)
result = cli.run(project=project, args=['workspace', 'list'])
result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA)
# Ensure that various versions that should be accepted are parsed
# correctly.
@pytest.mark.datafiles(DATA_DIR)
@pytest.mark.parametrize("workspace_cfg,expected", [
# Test loading version 0 without a dict
({
"alpha.bst": "/workspaces/bravo"
}, {
"format-version": BST_WORKSPACE_FORMAT_VERSION,
"workspaces": {
"alpha.bst": {
"prepared": False,
"path": "/workspaces/bravo",
"running_files": {}
}
}
}),
# Test loading version 0 with only one source
({
"alpha.bst": {
0: "/workspaces/bravo"
}
}, {
"format-version": BST_WORKSPACE_FORMAT_VERSION,
"workspaces": {
"alpha.bst": {
"prepared": False,
"path": "/workspaces/bravo",
"running_files": {}
}
}
}),
# Test loading version 1
({
"format-version": 1,
"workspaces": {
"alpha.bst": {
"path": "/workspaces/bravo"
}
}
}, {
"format-version": BST_WORKSPACE_FORMAT_VERSION,
"workspaces": {
"alpha.bst": {
"prepared": False,
"path": "/workspaces/bravo",
"running_files": {}
}
}
}),
# Test loading version 2
({
"format-version": 2,
"workspaces": {
"alpha.bst": {
"path": "/workspaces/bravo",
"last_successful": "some_key",
"running_files": {
"beta.bst": ["some_file"]
}
}
}
}, {
"format-version": BST_WORKSPACE_FORMAT_VERSION,
"workspaces": {
"alpha.bst": {
"prepared": False,
"path": "/workspaces/bravo",
"last_successful": "some_key",
"running_files": {
"beta.bst": ["some_file"]
}
}
}
}),
# Test loading version 3
({
"format-version": 3,
"workspaces": {
"alpha.bst": {
"prepared": True,
"path": "/workspaces/bravo",
"running_files": {}
}
}
}, {
"format-version": BST_WORKSPACE_FORMAT_VERSION,
"workspaces": {
"alpha.bst": {
"prepared": True,
"path": "/workspaces/bravo",
"running_files": {}
}
}
})
])
def test_list_supported_workspace(cli, tmpdir, datafiles, workspace_cfg, expected):
def parse_dict_as_yaml(node):
tempfile = os.path.join(str(tmpdir), 'yaml_dump')
_yaml.dump(node, tempfile)
return _yaml.node_sanitize(_yaml.load(tempfile))
project = os.path.join(datafiles.dirname, datafiles.basename)
os.makedirs(os.path.join(project, '.bst'))
workspace_config_path = os.path.join(project, '.bst', 'workspaces.yml')
_yaml.dump(workspace_cfg, workspace_config_path)
# Check that we can still read workspace config that is in old format
result = cli.run(project=project, args=['workspace', 'list'])
result.assert_success()
loaded_config = _yaml.node_sanitize(_yaml.load(workspace_config_path))
# Check that workspace config remains the same if no modifications
# to workspaces were made
assert loaded_config == parse_dict_as_yaml(workspace_cfg)
# Create a test bst file
bin_files_path = os.path.join(project, 'files', 'bin-files')
element_path = os.path.join(project, 'elements')
element_name = 'workspace-test.bst'
workspace = os.path.join(str(tmpdir), 'workspace')
# Create our repo object of the given source type with
# the bin files, and then collect the initial ref.
#
repo = create_repo('git', str(tmpdir))
ref = repo.create(bin_files_path)
# Write out our test target
element = {
'kind': 'import',
'sources': [
repo.source_config(ref=ref)
]
}
_yaml.dump(element,
os.path.join(element_path,
element_name))
# Make a change to the workspaces file
result = cli.run(project=project, args=['workspace', 'open', element_name, workspace])
result.assert_success()
result = cli.run(project=project, args=['workspace', 'close', '--remove-dir', element_name])
result.assert_success()
# Check that workspace config is converted correctly if necessary
loaded_config = _yaml.node_sanitize(_yaml.load(workspace_config_path))
assert loaded_config == parse_dict_as_yaml(expected)