west/tests/conftest.py

320 lines
11 KiB
Python
Raw Normal View History

# Copyright (c) 2019, 2020 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
import os
import platform
import shlex
import shutil
import subprocess
import sys
import textwrap
from west import configuration as config
import pytest
GIT = shutil.which('git')
# If you change this, keep the docstring in repos_tmpdir() updated also.
MANIFEST_TEMPLATE = '''\
manifest:
defaults:
remote: test-local
remotes:
- name: test-local
url-base: THE_URL_BASE
projects:
- name: Kconfiglib
revision: zephyr
path: subdir/Kconfiglib
- name: tagged_repo
revision: v1.0
- name: net-tools
clone-depth: 1
west-commands: scripts/west-commands.yml
self:
path: zephyr
'''
#
# Test fixtures
#
@pytest.fixture(scope='session')
def _session_repos():
'''Just a helper, do not use directly.'''
# It saves time to create repositories once at session scope, then
# clone the results as needed in per-test fixtures.
session_repos = os.path.join(os.environ['TOXTEMPDIR'], 'session_repos')
print('initializing session repositories in', session_repos)
shutil.rmtree(session_repos, ignore_errors=True)
# Create the repositories.
rp = {} # individual repository paths
for repo in 'Kconfiglib', 'tagged_repo', 'net-tools', 'zephyr':
path = os.path.join(session_repos, repo)
rp[repo] = path
create_repo(path)
# Initialize the "zephyr" repository.
# The caller needs to add west.yml with the right url-base.
add_commit(rp['zephyr'], 'base zephyr commit',
files={'CODEOWNERS': '',
'include/header.h': '#pragma once\n',
'subsys/bluetooth/code.c': 'void foo(void) {}\n'})
# Initialize the Kconfiglib repository.
create_branch(rp['Kconfiglib'], 'zephyr', checkout=True)
add_commit(rp['Kconfiglib'], 'test kconfiglib commit',
files={'kconfiglib.py': 'print("hello world kconfiglib")\n'})
# Initialize the tagged_repo repository.
add_commit(rp['tagged_repo'], 'tagged_repo commit',
files={'test.txt': 'hello world'})
add_tag(rp['tagged_repo'], 'v1.0')
# Initialize the net-tools repository.
add_commit(rp['net-tools'], 'test net-tools commit',
files={'qemu-script.sh': 'echo hello world net-tools\n',
'scripts/west-commands.yml': textwrap.dedent('''\
west-commands:
- file: scripts/test.py
commands:
- name: test-extension
class: TestExtension
help: test-extension-help
'''),
'scripts/test.py': textwrap.dedent('''\
from west.commands import WestCommand
class TestExtension(WestCommand):
def __init__(self):
super().__init__('test-extension',
'test-extension-help',
'')
def do_add_parser(self, parser_adder):
parser = parser_adder.add_parser(self.name)
return parser
def do_run(self, args, ignored):
print('Testing test command 1')
'''),
})
# Return the top-level temporary directory. Don't clean it up on
# teardown, so the contents can be inspected post-portem.
print('finished initializing session repositories')
return session_repos
@pytest.fixture
def repos_tmpdir(tmpdir, _session_repos):
'''Fixture for tmpdir with "remote" repositories.
These can then be used to bootstrap a workspace and run
project-related commands on it with predictable results.
Switches directory to, and returns, the top level tmpdir -- NOT
the subdirectory containing the repositories themselves.
Initializes placeholder upstream repositories in tmpdir with the
following contents:
repos/
Kconfiglib (branch: zephyr)
kconfiglib.py
tagged_repo (branch: master, tag: v1.0)
test.txt
net-tools (branch: master)
qemu-script.sh
zephyr (branch: master)
CODEOWNERS
west.yml
include
header.h
subsys
bluetooth
code.c
The contents of west.yml are:
manifest:
defaults:
remote: test-local
remotes:
- name: test-local
url-base: file://<tmpdir>/repos
projects:
- name: Kconfiglib
revision: zephyr
path: subdir/Kconfiglib
- name: tagged_repo
revision: v1.0
- name: net-tools
clone-depth: 1
west-commands: scripts/west-commands.yml
self:
path: zephyr
'''
kconfiglib, tagged_repo, net_tools, zephyr = [
os.path.join(_session_repos, x) for x in
['Kconfiglib', 'tagged_repo', 'net-tools', 'zephyr']]
repos = tmpdir.mkdir('repos')
repos.chdir()
for r in [kconfiglib, tagged_repo, net_tools, zephyr]:
subprocess.check_call([GIT, 'clone', r])
manifest = MANIFEST_TEMPLATE.replace('THE_URL_BASE',
str(tmpdir.join('repos')))
add_commit(str(repos.join('zephyr')), 'add manifest',
files={'west.yml': manifest})
return tmpdir
@pytest.fixture
def west_init_tmpdir(repos_tmpdir):
'''Fixture for a tmpdir with 'remote' repositories and 'west init' run.
Uses the remote repositories from the repos_tmpdir fixture to
create a west workspace using the system bootstrapper's init
command.
The contents of the west workspace aren't checked at all.
This is left up to the test cases.
The directory that 'west init' created is returned as a
py.path.local, with the current working directory set there.'''
west_tmpdir = repos_tmpdir / 'workspace'
manifest = repos_tmpdir / 'repos' / 'zephyr'
cmd(f'init -m "{manifest}" "{west_tmpdir}"')
west_tmpdir.chdir()
config.read_config()
return west_tmpdir
#
# Helper functions
#
def check_output(*args, **kwargs):
# Like subprocess.check_output, but returns a string in the
# default encoding instead of a byte array.
try:
out_bytes = subprocess.check_output(*args, **kwargs)
except subprocess.CalledProcessError as e:
print('*** check_output: nonzero return code', e.returncode,
file=sys.stderr)
print('cwd =', os.getcwd(), 'args =', args,
'kwargs =', kwargs, file=sys.stderr)
print('subprocess output:', file=sys.stderr)
print(e.output.decode(), file=sys.stderr)
raise
return out_bytes.decode(sys.getdefaultencoding())
def cmd(cmd, cwd=None, stderr=None, env=None):
# Run a west command in a directory (cwd defaults to os.getcwd()).
#
# This helper takes the command as a string.
#
# This helper relies on the test environment to ensure that the
# 'west' executable is a bootstrapper installed from the current
# west source code.
#
# stdout from cmd is captured and returned. The command is run in
# a python subprocess so that program-level setup and teardown
# happen fresh.
cmd = 'west ' + cmd
if platform.system() != 'Windows':
cmd = shlex.split(cmd)
print('running:', cmd)
if env:
print('with non-default environment:')
for k in env:
if k not in os.environ or env[k] != os.environ[k]:
print(f'\t{k}={env[k]}')
for k in os.environ:
if k not in env:
print(f'\t{k}: deleted, was: {os.environ[k]}')
try:
return check_output(cmd, cwd=cwd, stderr=stderr, env=env)
except subprocess.CalledProcessError:
print('cmd: west:', shutil.which('west'), file=sys.stderr)
raise
def create_repo(path):
# Initializes a Git repository in 'path', and adds an initial commit to it
subprocess.check_call([GIT, 'init', path])
config_repo(path)
add_commit(path, 'initial')
def config_repo(path):
# Set name and email. This avoids a "Please tell me who you are" error when
# there's no global default.
subprocess.check_call([GIT, 'config', 'user.name', 'West Test'], cwd=path)
subprocess.check_call([GIT, 'config', 'user.email',
'west-test@example.com'],
cwd=path)
def create_branch(path, branch, checkout=False):
subprocess.check_call([GIT, 'branch', branch], cwd=path)
if checkout:
checkout_branch(path, branch)
def checkout_branch(path, branch, detach=False):
detach = ['--detach'] if detach else []
subprocess.check_call([GIT, 'checkout', branch] + detach,
cwd=path)
def add_commit(repo, msg, files=None, reconfigure=True):
# Adds a commit with message 'msg' to the repo in 'repo'
#
# If 'files' is given, it must be a dictionary mapping files to
# edit to the contents they should contain in the new
# commit. Otherwise, the commit will be empty.
#
# If 'reconfigure' is True, the user.name and user.email git
# configuration variables will be set in 'repo' using config_repo().
repo = str(repo)
if reconfigure:
config_repo(repo)
# Edit any files as specified by the user and add them to the index.
if files:
for path, contents in files.items():
dirname, basename = os.path.dirname(path), os.path.basename(path)
fulldir = os.path.join(repo, dirname)
if not os.path.isdir(fulldir):
# Allow any errors (like trying to create a directory
# where a file already exists) to propagate up.
os.makedirs(fulldir)
with open(os.path.join(fulldir, basename), 'w') as f:
f.write(contents)
subprocess.check_call([GIT, 'add', path], cwd=repo)
# The extra '--no-xxx' flags are for convenience when testing
# on developer workstations, which may have global git
# configuration to sign commits, etc.
#
# We don't want any of that, as it could require user
# intervention or fail in environments where Git isn't
# configured.
subprocess.check_call(
[GIT, 'commit', '-a', '--allow-empty', '-m', msg, '--no-verify',
'--no-gpg-sign', '--no-post-rewrite'], cwd=repo)
def add_tag(repo, tag, commit='HEAD', msg=None):
if msg is None:
msg = 'tag ' + tag
# Override tag.gpgSign with --no-sign, in case the test
# environment has that set to true.
subprocess.check_call([GIT, 'tag', '-m', msg, '--no-sign', tag, commit],
cwd=repo)
def rev_parse(repo, revision):
out = subprocess.check_output([GIT, 'rev-parse', revision], cwd=repo)
return out.decode(sys.getdefaultencoding())