Browse Source

New smoke tests based on pytest

default_compile_flags
vector-of-bool 5 years ago
parent
commit
62b60c2dfe
15 changed files with 427 additions and 0 deletions
  1. +1
    -0
      tests/__init__.py
  2. +14
    -0
      tests/basics/test_app_only.py
  3. +44
    -0
      tests/basics/test_basics.py
  4. +30
    -0
      tests/basics/test_simple.py
  5. +14
    -0
      tests/basics/test_test_only.py
  6. +38
    -0
      tests/conftest.py
  7. +195
    -0
      tests/dds.py
  8. +25
    -0
      tests/deps/do_test.py
  9. +4
    -0
      tests/deps/git-remote/package.dds
  10. +1
    -0
      tests/deps/git-remote/remote.dds
  11. +2
    -0
      tests/deps/no-deps/package.dds
  12. +0
    -0
      tests/deps/no-deps/remote.dds
  13. +7
    -0
      tests/errors/errors_test.py
  14. +52
    -0
      tests/fileutil.py
  15. +0
    -0
      tests/proc.py

+ 1
- 0
tests/__init__.py View File

from .dds import DDS, DDSFixtureParams, scoped_dds, dds_fixture_conf, dds_fixture_conf_1

+ 14
- 0
tests/basics/test_app_only.py View File

from contextlib import ExitStack
from tests import DDS
from tests.fileutil import set_contents


def test_lib_with_just_app(dds: DDS, scope: ExitStack):
scope.enter_context(
set_contents(
dds.source_root / 'src/foo.main.cpp',
b'int main() {}',
))

dds.build()
assert (dds.build_dir / f'foo{dds.exe_suffix}').is_file()

+ 44
- 0
tests/basics/test_basics.py View File

from contextlib import contextmanager
from tests import DDS
from tests.fileutil import ensure_dir, set_contents


def test_build_empty(dds: DDS):
assert not dds.source_root.exists()
dds.scope.enter_context(ensure_dir(dds.source_root))
dds.build()


def test_build_simple(dds: DDS):
dds.scope.enter_context(
set_contents(dds.source_root / 'src/f.cpp', b'void foo() {}'))
dds.build()


def basic_pkg_dds(dds: DDS):
return set_contents(
dds.source_root / 'package.dds', b'''
Name: test-pkg
Version: 0.2.2
''')


def test_empty_with_pkg_dds(dds: DDS):
dds.scope.enter_context(basic_pkg_dds(dds))
dds.build()


def test_empty_with_lib_dds(dds: DDS):
dds.scope.enter_context(basic_pkg_dds(dds))
dds.build()
pass


def test_empty_sdist_create(dds: DDS):
dds.scope.enter_context(basic_pkg_dds(dds))
dds.sdist_create()


def test_empty_sdist_export(dds: DDS):
dds.scope.enter_context(basic_pkg_dds(dds))
dds.sdist_export()

+ 30
- 0
tests/basics/test_simple.py View File

from contextlib import ExitStack
from tests import DDS
from tests.fileutil import set_contents


def test_simple_lib(dds: DDS, scope: ExitStack):
scope.enter_context(
dds.set_contents(
'src/foo.cpp',
b'int the_answer() { return 42; }',
))

scope.enter_context(
dds.set_contents(
'library.dds',
b'Name: TestLibrary',
))

scope.enter_context(
dds.set_contents(
'package.dds',
b'''
Name: TestProject
Version: 0.0.0
''',
))

dds.build(tests=True, apps=False, warnings=False, export=True)
assert (dds.build_dir / 'compile_commands.json').is_file()
assert list(dds.build_dir.glob('libTestLibrary*')) != []

+ 14
- 0
tests/basics/test_test_only.py View File

from contextlib import ExitStack
from tests import DDS
from tests.fileutil import set_contents


def test_lib_with_just_test(dds: DDS, scope: ExitStack):
scope.enter_context(
set_contents(
dds.source_root / 'src/foo.test.cpp',
b'int main() {}',
))

dds.build(tests=True, apps=False, warnings=False, export=False)
assert (dds.build_dir / f'test/foo{dds.exe_suffix}').is_file()

+ 38
- 0
tests/conftest.py View File

from contextlib import ExitStack
from typing import Optional
from pathlib import Path
import shutil

import pytest

from tests import scoped_dds, DDSFixtureParams


@pytest.yield_fixture
def dds(request, tmp_path: Path, worker_id: str, scope: ExitStack):
test_source_dir = Path(request.fspath).absolute().parent
test_root = test_source_dir

# If we are running in parallel, use a unique directory as scratch
# space so that we aren't stomping on anyone else
if worker_id != 'master':
test_root = tmp_path / request.function.__name__
shutil.copytree(test_source_dir, test_root)

project_dir = test_root / 'project'
# Check if we have a special configuration
if hasattr(request, 'param'):
assert isinstance(request.param, DDSFixtureParams), \
('Using the `dds` fixture requires passing in indirect '
'params. Use @dds_fixture_conf to configure the fixture')
params: DDSFixtureParams = request.param
project_dir = test_root / params.subdir

# Create the instance. Auto-clean when we're done
yield scope.enter_context(scoped_dds(test_root, project_dir, request.function.__name__))


@pytest.fixture
def scope():
with ExitStack() as scope:
yield scope

+ 195
- 0
tests/dds.py View File

import os
import itertools
from contextlib import contextmanager, ExitStack
from pathlib import Path
from typing import Iterable, Union, Any, Dict, NamedTuple, ContextManager
import subprocess
import shutil

import pytest

from . import fileutil

CommandLineArg = Union[str, Path, int, float]
CommandLineArg1 = Union[CommandLineArg, Iterable[CommandLineArg]]
CommandLineArg2 = Union[CommandLineArg1, Iterable[CommandLineArg1]]
CommandLineArg3 = Union[CommandLineArg2, Iterable[CommandLineArg2]]
CommandLineArg4 = Union[CommandLineArg3, Iterable[CommandLineArg3]]
CommandLine = Iterable[CommandLineArg4]


def _flatten_cmd(cmd: CommandLine) -> Iterable[str]:
if isinstance(cmd, (str, Path)):
yield str(cmd)
elif isinstance(cmd, (int, float)):
yield str(cmd)
elif hasattr(cmd, '__iter__'):
each = (_flatten_cmd(arg) for arg in cmd) # type: ignore
for item in each:
yield from item
else:
assert False, f'Invalid command line element: {repr(cmd)}'


class DDS:
def __init__(self, dds_exe: Path, test_dir: Path, project_dir: Path,
scope: ExitStack) -> None:
self.dds_exe = dds_exe
self.test_dir = test_dir
self.source_root = project_dir
self.scratch_dir = project_dir / '_test_scratch'
self.scope = scope
self.scope.callback(self.cleanup)

@property
def repo_dir(self) -> Path:
return self.scratch_dir / 'repo'

@property
def deps_build_dir(self) -> Path:
return self.scratch_dir / 'deps-build'

@property
def build_dir(self) -> Path:
return self.scratch_dir / 'build'

@property
def lmi_path(self) -> Path:
return self.scratch_dir / 'INDEX.lmi'

def cleanup(self):
if self.scratch_dir.exists():
shutil.rmtree(self.scratch_dir)

def run_unchecked(self, cmd: CommandLine, *,
cwd: Path = None) -> subprocess.CompletedProcess:
full_cmd = list(_flatten_cmd(itertools.chain([self.dds_exe], cmd)))
return subprocess.run(
full_cmd,
cwd=cwd or self.source_root,
# stdout=subprocess.PIPE,
# stderr=subprocess.STDOUT,
)

def run(self, cmd: CommandLine, *,
cwd: Path = None) -> subprocess.CompletedProcess:
cmdline = list(_flatten_cmd(cmd))
res = self.run_unchecked(cmd)
if res.returncode != 0:
raise subprocess.CalledProcessError(
res.returncode, [self.dds_exe] + cmdline, res.stdout)
return res

@property
def repo_dir_arg(self) -> str:
return f'--repo-dir={self.repo_dir}'

@property
def project_dir_arg(self) -> str:
return f'--project-dir={self.source_root}'

def deps_ls(self) -> subprocess.CompletedProcess:
return self.run(['deps', 'ls'])

def deps_get(self) -> subprocess.CompletedProcess:
return self.run([
'deps',
'get',
self.repo_dir_arg,
])

def deps_build(self, *,
toolchain: str = None) -> subprocess.CompletedProcess:
return self.run([
'deps',
'build',
f'--toolchain={toolchain or self.default_builtin_toolchain}',
self.repo_dir_arg,
f'--deps-build-dir={self.deps_build_dir}',
f'--lmi-path={self.lmi_path}',
])

def build(self,
*,
toolchain: str = None,
apps: bool = True,
warnings: bool = True,
tests: bool = True,
export: bool = False) -> subprocess.CompletedProcess:
return self.run([
'build',
f'--out={self.build_dir}',
['--tests'] if tests else [],
['--apps'] if apps else [],
['--warnings'] if warnings else [],
['--export'] if export else [],
f'--toolchain={toolchain or self.default_builtin_toolchain}',
self.project_dir_arg,
])

def sdist_create(self) -> subprocess.CompletedProcess:
return self.run([
'sdist',
'create',
self.project_dir_arg,
f'--out={self.build_dir / "stuff.sds"}',
])

def sdist_export(self) -> subprocess.CompletedProcess:
return self.run([
'sdist',
'export',
self.project_dir_arg,
self.repo_dir_arg,
])

@property
def default_builtin_toolchain(self) -> str:
if os.name == 'posix':
return ':gcc-8'
elif os.name == 'nt':
return ':msvc'
else:
raise RuntimeError(
f'No default builtin toolchain defined for tests on platform "{os.name}"'
)

@property
def exe_suffix(self) -> str:
if os.name == 'posix':
return ''
elif os.name == 'nt':
return '.exe'
else:
raise RuntimeError(
f'We don\'t know the executable suffix for the platform "{os.name}"'
)

def set_contents(self, path: Union[str, Path],
content: bytes) -> ContextManager[Path]:
return fileutil.set_contents(self.source_root / path, content)


@contextmanager
def scoped_dds(test_dir: Path, project_dir: Path, name: str):
dds_exe = Path(__file__).absolute().parent.parent / '_build/dds'
if os.name == 'nt':
dds_exe = dds_exe.with_suffix('.exe')
with ExitStack() as scope:
yield DDS(dds_exe, test_dir, project_dir, scope)


class DDSFixtureParams(NamedTuple):
ident: str
subdir: Union[Path, str]


def dds_fixture_conf(*argsets: DDSFixtureParams):
args = list(argsets)
return pytest.mark.parametrize(
'dds', args, indirect=True, ids=[p.ident for p in args])


def dds_fixture_conf_1(subdir: Union[Path, str]):
params = DDSFixtureParams(ident='only', subdir=subdir)
return pytest.mark.parametrize('dds', [params], indirect=True, ids=['.'])

+ 25
- 0
tests/deps/do_test.py View File

import pytest
import subprocess

from tests import DDS, DDSFixtureParams, dds_fixture_conf

dds_conf = dds_fixture_conf(
DDSFixtureParams(ident='git-remote', subdir='git-remote'),
DDSFixtureParams(ident='no-deps', subdir='no-deps'),
)


@dds_conf
def test_ls(dds: DDS):
dds.run(['deps', 'ls'])


@dds_conf
def test_deps_build(dds: DDS):
assert not dds.repo_dir.exists()
dds.deps_get()
assert dds.repo_dir.exists(), '`deps get` did not generate a repo directory'

assert not dds.lmi_path.exists()
dds.deps_build()
assert dds.lmi_path.exists(), '`deps build` did not generate the build dir'

+ 4
- 0
tests/deps/git-remote/package.dds View File

Name: deps-test
Version: 0.0.0

Depends: neo-buffer 0.1.0

+ 1
- 0
tests/deps/git-remote/remote.dds View File

Remote-Package: neo-buffer 0.1.0; git url=git@github.com:vector-of-bool/neo-buffer.git ref=develop

+ 2
- 0
tests/deps/no-deps/package.dds View File

Name: deps-test
Version: 0.0.0

+ 0
- 0
tests/deps/no-deps/remote.dds View File


+ 7
- 0
tests/errors/errors_test.py View File

from tests import DDS
from tests.fileutil import ensure_dir


def test_empty_dir(dds: DDS):
with ensure_dir(dds.source_root):
dds.build()

+ 52
- 0
tests/fileutil.py View File

from contextlib import contextmanager, ExitStack
from pathlib import Path
from typing import Iterator, Union, Optional

import shutil

@contextmanager
def ensure_dir(dirpath: Path) -> Iterator[Path]:
"""
Ensure that the given directory (and any parents) exist. When the context
exists, removes any directories that were created.
"""
dirpath = dirpath.absolute()
if dirpath.exists():
assert dirpath.is_dir(), f'Directory {dirpath} is a non-directory file'
yield dirpath
return

# Create the directory and clean it up when we are done
with ensure_dir(dirpath.parent):
dirpath.mkdir()
try:
yield dirpath
finally:
shutil.rmtree(dirpath)


@contextmanager
def auto_delete(fpath: Path) -> Iterator[Path]:
try:
yield fpath
finally:
if fpath.exists():
fpath.unlink()


@contextmanager
def set_contents(fpath: Path, content: bytes) -> Iterator[Path]:
prev_content: Optional[bytes] = None
if fpath.exists():
assert fpath.is_file(), 'File {fpath} exists and is not a regular file'
prev_content = fpath.read_bytes()

with ensure_dir(fpath.parent):
fpath.write_bytes(content)
try:
yield fpath
finally:
if prev_content is None:
fpath.unlink()
else:
fpath.write_bytes(prev_content)

+ 0
- 0
tests/proc.py View File


Loading…
Cancel
Save