Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Cleanup tests #115

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ install:
# Install python packages
- "%PYTHON%/Scripts/pip.exe install twine"
- "%PYTHON%/Scripts/pip.exe install codecov"
- "%PYTHON%/Scripts/pip.exe install pytest"

test_script:
# test/hello
- "%PYTHON%/python setup.py develop"
- "%PYTHON%/Scripts/coverage run test/test.py"
- "%PYTHON%/Scripts/coverage run -m pytest test"
- "%PYTHON%/Scripts/coverage combine"
- "%PYTHON%/Scripts/codecov"
#- "%PYTHON%/python setup.py develop"
Expand Down
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ before_install:
install:
- pip3 install setuptools
- pip3 install codecov
- pip3 install pytest
- python3 setup.py develop

script:
Expand Down
22 changes: 17 additions & 5 deletions clang_build/build_type.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
from enum import Enum as _Enum


class BuildType(_Enum):
Default = 'default'
Release = 'release'
RelWithDebInfo = 'relwithdebinfo'
Debug = 'debug'
Coverage = 'coverage'
"""Enumeration of all build types.

Construction is case sensitive, i.e.

>>> BuildType("default") == BuildType("Default")
True

"""

Default = "default"
Release = "release"
RelWithDebInfo = "relwithdebinfo"
Debug = "debug"
Coverage = "coverage"

def __str__(self):
"""Return the value of the BuildType."""
return self.value

# Let's be case insensitive
@classmethod
def _missing_(cls, value):
"""Check for identical value except for caseing."""
for item in cls:
if item.value.lower() == value.lower():
return item
Expand Down
20 changes: 14 additions & 6 deletions clang_build/clang_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ def _setup_logger(log_level=None):
ch.setFormatter(formatter)
_LOGGER.addHandler(ch)

def _check_positive(value):
value = int(value)
if value <= 0:
raise _argparse.ArgumentTypeError("%s is negative")

return value

def parse_args(args):
_command_line_description = (
Expand All @@ -60,6 +66,7 @@ def parse_args(args):
action='store_true')
parser.add_argument('-d', '--directory',
type=_Path,
default=_Path(),
help='set the root source directory')
parser.add_argument('-b', '--build-type',
choices=list(_BuildType),
Expand All @@ -78,7 +85,7 @@ def parse_args(args):
help='also build sources which have already been built',
action='store_true')
parser.add_argument('-j', '--jobs',
type=int,
type=_check_positive,
default=1,
help='set the number of concurrent build jobs')
parser.add_argument('--debug',
Expand All @@ -103,14 +110,15 @@ def build(args):
# Create container of environment variables
environment = _Environment(vars(args))

categories = ['Configure', 'Compile', 'Link']
if environment.bundle:
categories.append('Generate bundle')
if environment.redistributable:
categories.append('Generate redistributable')
categories = ['Configure', 'Build']
#if environment.bundle:
# categories.append('Generate bundle')
#if environment.redistributable:
# categories.append('Generate redistributable')

with _CategoryProgress(categories, not args.progress) as progress_bar:
project = _Project.from_directory(args.directory, environment)
progress_bar.update()
project.build(args.all, args.targets, args.jobs)
progress_bar.update()

Expand Down
4 changes: 2 additions & 2 deletions clang_build/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def _get_driver(self, source_file):
else:
return [str(self.clangpp), self.max_cpp_dialect]

def compile(self, source_file, object_file, flags):
def compile(self, source_file, object_file, flags=None):
"""Compile a given source file into an object file.

If the object file is placed into a non-existing folder, this
Expand Down Expand Up @@ -191,7 +191,7 @@ def compile(self, source_file, object_file, flags):
return self._run_clang_command(
self._get_driver(source_file)
+ ["-c", str(source_file), "-o", str(object_file)]
+ flags
+ (flags if flags else [])
)

def generate_dependency_file(self, source_file, dependency_file, flags):
Expand Down
25 changes: 13 additions & 12 deletions clang_build/directories.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
from copy import copy

class Directories:

def include_public_total(self):
includes = copy(self.include_public)
for target in self.dependencies:
includes += target.directories.include_public_total()

return includes

def __init__(self, files, dependencies):
self.dependencies = dependencies

# Include directories
self.include_private = files["include_directories"]
self.include_public = files["include_directories_public"]

# Default include path
# if self.root_directory.joinpath('include').exists():
# self._include_directories_public = [self.root_directory.joinpath('include')] + self._include_directories_public

# Public include directories of dependencies are forwarded
for target in self.dependencies:
self.include_public += target.directories.include_public

# Make unique and resolve
self.include_private = list(
Expand All @@ -23,9 +26,7 @@ def __init__(self, files, dependencies):
)

def final_directories_list(self):
return list(dict.fromkeys(
self.include_private + self.include_public
))
return list(dict.fromkeys(self.include_private + self.include_public_total()))

def include_command(self):
include_directories_command = []
Expand All @@ -35,5 +36,5 @@ def include_command(self):
return include_directories_command

def make_private_directories_public(self):
self.include_public = self.final_directories_list()
self.include_private = []
self.include_public = self.include_private + self.include_public
self.include_private = []
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ packages = clang_build

[entry_points]
console_scripts =
clang-build = clang_build.clang_build:_main
clang-build = clang_build.clang_build:_main

[aliases]
test=pytest
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
setuptools.setup(
python_requires='>=3.7',
setup_requires=['pbr<4'],
pbr=True)
pbr=True,
tests_require=['pytest'])
Empty file removed test/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions test/test_build_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from clang_build.build_type import BuildType

def test_case_insensitivity():
assert BuildType("default") == BuildType("Default")
7 changes: 7 additions & 0 deletions test/test_circle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from clang_build.circle import Circle

def test_string_representation():
c = Circle(["A", "B", "C"])

assert str(c) == "A -> B -> C"
assert repr(c) == f'{repr("A")} -> {repr("B")} -> {repr("C")}'
59 changes: 59 additions & 0 deletions test/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import subprocess
from pathlib import Path
from shutil import rmtree

import pytest

from clang_build import clang_build as cli
from clang_build.build_type import BuildType


def cli_argument_check(
argument, argument_short, default_value, custom_parameter, expected_outcome
):
args_argument = argument.replace("-", "_")
# default check
args = cli.parse_args([])
assert vars(args)[args_argument] == default_value

# custom input
args = cli.parse_args([f"--{argument}"] + custom_parameter)
assert vars(args)[args_argument] == expected_outcome

if argument_short:
args = cli.parse_args([f"-{argument_short}"] + custom_parameter)
assert vars(args)[args_argument] == expected_outcome


def test_cli_arguments():
cli_argument_check("verbose", "V", False, [], True)
cli_argument_check("progress", "p", False, [], True)
cli_argument_check("directory", "d", Path(), ["my_folder"], Path("my_folder"))
cli_argument_check("build-type", "b", BuildType.Default, ["dEbUg"], BuildType.Debug)
cli_argument_check("all", "a", False, [], True)
cli_argument_check(
"targets", "t", None, ["target1", "target2"], ["target1", "target2"]
)
with pytest.raises(SystemExit):
cli_argument_check("all", "a", False, ["--targets", "target1"], False)
cli_argument_check("force-build", "f", False, [], True)
cli_argument_check("jobs", "j", 1, ["12"], 12)
with pytest.raises(SystemExit):
cli_argument_check("jobs", "j", 1, ["0"], 0)
cli_argument_check("debug", None, False, [], True)
cli_argument_check("no-graph", None, False, [], True)
cli_argument_check("bundle", None, False, [], True)
cli_argument_check("redistributable", None, False, [], True)


def test_hello_world_mwe():
try:
cli.build(cli.parse_args(["-d", "test/mwe"]))
output = (
subprocess.check_output(["./build/default/bin/main"], stderr=subprocess.STDOUT)
.decode("utf-8")
.strip()
)
assert output == "Hello!"
finally:
rmtree("build", ignore_errors=True)
87 changes: 87 additions & 0 deletions test/test_compiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from pathlib import Path
import subprocess

from clang_build.compiler import Clang


def test_finds_clang():
compiler = Clang()

for exe in [compiler.clang, compiler.clangpp, compiler.clang_ar]:
subprocess.check_call([str(exe), "--version"])


def create_file(content, path):
with open(path, 'w') as f:
f.write(content)

def remove_file(path):
path.unlink(missing_ok=True)

def remove_dir(path):
path.rmdir()


def test_compile_empty_source():
compiler = Clang()
source_file = Path("empty.cpp")
object_file = Path("output.o")
try:
create_file("", source_file)
success, _ = compiler.compile(source_file, object_file)
assert object_file.exists()
assert success
finally:
remove_file(object_file)
remove_file(source_file)

def test_compile_faulty_source():
compiler = Clang()
source_file = Path("faulty.cpp")
object_file = Path("should_not_be_here.o")
try:
create_file("{", source_file)
success, report = compiler.compile(source_file, object_file)
assert not success
assert str(source_file)+":1:1" in report
finally:
remove_file(object_file)
remove_file(source_file)

def test_compile_with_flags():
compiler = Clang()
source_file = Path("needs_flags.cpp")
object_file = Path("should_not_be_here.o")
try:
create_file("int main(){\n#ifdef HIFLAG\n}\n#endif", source_file)
success, _ = compiler.compile(source_file, object_file)
assert not success
success, _ = compiler.compile(source_file, object_file, ["-DHIFLAG"])
assert success
assert object_file.exists()
finally:
remove_file(object_file)
remove_file(source_file)

def test_compile_output_in_folder():
compiler = Clang()
source_file = Path("empty.cpp")
object_file = Path("nested/folder/output.o")
try:
create_file("", source_file)
success, _ = compiler.compile(source_file, object_file)
assert success
assert object_file.exists()
finally:
remove_file(object_file)
remove_dir(object_file.parent)
remove_dir(object_file.parent.parent)
remove_file(source_file)


def test_link():
assert False


def test_dependency_file():
assert False
Loading