[style] | |||||
based_on_style = pep8 | |||||
column_limit = 120 |
} | } | ||||
}, | }, | ||||
} | } | ||||
dds.scope.enter_context( | |||||
dds.set_contents(json_fpath, | |||||
json.dumps(import_data).encode())) | |||||
dds.scope.enter_context(dds.set_contents(json_fpath, json.dumps(import_data).encode())) | |||||
dds.catalog_import(json_fpath) | dds.catalog_import(json_fpath) | ||||
@pytest.yield_fixture | @pytest.yield_fixture | ||||
def http_import_server(): | def http_import_server(): | ||||
handler = partial( | |||||
DirectoryServingHTTPRequestHandler, | |||||
dir=Path.cwd() / 'data/http-test-1') | |||||
handler = partial(DirectoryServingHTTPRequestHandler, dir=Path.cwd() / 'data/http-test-1') | |||||
addr = ('0.0.0.0', 8000) | addr = ('0.0.0.0', 8000) | ||||
pool = ThreadPoolExecutor() | pool = ThreadPoolExecutor() | ||||
with HTTPServer(addr, handler) as httpd: | with HTTPServer(addr, handler) as httpd: | ||||
@pytest.yield_fixture | @pytest.yield_fixture | ||||
def http_repo_server(): | def http_repo_server(): | ||||
handler = partial( | |||||
DirectoryServingHTTPRequestHandler, | |||||
dir=Path.cwd() / 'data/test-repo-1') | |||||
handler = partial(DirectoryServingHTTPRequestHandler, dir=Path.cwd() / 'data/test-repo-1') | |||||
addr = ('0.0.0.0', 4646) | addr = ('0.0.0.0', 4646) | ||||
pool = ThreadPoolExecutor() | pool = ThreadPoolExecutor() | ||||
with HTTPServer(addr, handler) as httpd: | with HTTPServer(addr, handler) as httpd: | ||||
pool.submit(lambda: httpd.serve_forever(poll_interval=0.1)) | pool.submit(lambda: httpd.serve_forever(poll_interval=0.1)) | ||||
try: | try: | ||||
yield | |||||
yield 'http://localhost:4646' | |||||
finally: | finally: | ||||
httpd.shutdown() | httpd.shutdown() | ||||
def test_import_http(dds: DDS, http_import_server): | |||||
dds.repo_dir.mkdir(parents=True, exist_ok=True) | |||||
dds.run( | |||||
[ | |||||
'repo', | |||||
dds.repo_dir_arg, | |||||
'import', | |||||
'http://localhost:8000/neo-buffer-0.4.2.tar.gz', | |||||
], | |||||
cwd=dds.repo_dir, | |||||
) | |||||
assert dds.repo_dir.joinpath('neo-buffer@0.4.2').is_dir() | |||||
def test_repo_add(dds: DDS, http_repo_server): | def test_repo_add(dds: DDS, http_repo_server): | ||||
dds.repo_dir.mkdir(parents=True, exist_ok=True) | dds.repo_dir.mkdir(parents=True, exist_ok=True) | ||||
dds.run([ | dds.run([ | ||||
dds.repo_dir_arg, | dds.repo_dir_arg, | ||||
'add', | 'add', | ||||
dds.catalog_path_arg, | dds.catalog_path_arg, | ||||
'http://localhost:4646', | |||||
http_repo_server, | |||||
'--update', | '--update', | ||||
]) | ]) | ||||
# dds.build_deps(['neo-url@0.2.1']) | |||||
dds.build_deps(['neo-fun@0.6.0']) |
class DDS: | class DDS: | ||||
def __init__(self, dds_exe: Path, test_dir: Path, project_dir: Path, | |||||
scope: ExitStack) -> None: | |||||
def __init__(self, dds_exe: Path, test_dir: Path, project_dir: Path, scope: ExitStack) -> None: | |||||
self.dds_exe = dds_exe | self.dds_exe = dds_exe | ||||
self.test_dir = test_dir | self.test_dir = test_dir | ||||
self.source_root = project_dir | self.source_root = project_dir | ||||
if self.scratch_dir.exists(): | if self.scratch_dir.exists(): | ||||
shutil.rmtree(self.scratch_dir) | shutil.rmtree(self.scratch_dir) | ||||
def run_unchecked(self, cmd: proc.CommandLine, *, | |||||
cwd: Path = None) -> subprocess.CompletedProcess: | |||||
def run_unchecked(self, cmd: proc.CommandLine, *, cwd: Path = None) -> subprocess.CompletedProcess: | |||||
full_cmd = itertools.chain([self.dds_exe, '-ltrace'], cmd) | full_cmd = itertools.chain([self.dds_exe, '-ltrace'], cmd) | ||||
return proc.run(full_cmd, cwd=cwd or self.source_root) | return proc.run(full_cmd, cwd=cwd or self.source_root) | ||||
def run(self, cmd: proc.CommandLine, *, cwd: Path = None, | |||||
check=True) -> subprocess.CompletedProcess: | |||||
def run(self, cmd: proc.CommandLine, *, cwd: Path = None, check=True) -> subprocess.CompletedProcess: | |||||
cmdline = list(proc.flatten_cmd(cmd)) | cmdline = list(proc.flatten_cmd(cmd)) | ||||
res = self.run_unchecked(cmd, cwd=cwd) | res = self.run_unchecked(cmd, cwd=cwd) | ||||
if res.returncode != 0 and check: | if res.returncode != 0 and check: | ||||
raise subprocess.CalledProcessError( | |||||
res.returncode, [self.dds_exe] + cmdline, res.stdout) | |||||
raise subprocess.CalledProcessError(res.returncode, [self.dds_exe] + cmdline, res.stdout) | |||||
return res | return res | ||||
@property | @property | ||||
def catalog_path_arg(self) -> str: | def catalog_path_arg(self) -> str: | ||||
return f'--catalog={self.catalog_path}' | return f'--catalog={self.catalog_path}' | ||||
def build_deps(self, args: proc.CommandLine, *, | |||||
toolchain: str = None) -> subprocess.CompletedProcess: | |||||
def build_deps(self, args: proc.CommandLine, *, toolchain: str = None) -> subprocess.CompletedProcess: | |||||
return self.run([ | return self.run([ | ||||
'build-deps', | 'build-deps', | ||||
f'--toolchain={toolchain or self.default_builtin_toolchain}', | f'--toolchain={toolchain or self.default_builtin_toolchain}', | ||||
def sdist_create(self) -> subprocess.CompletedProcess: | def sdist_create(self) -> subprocess.CompletedProcess: | ||||
self.build_dir.mkdir(exist_ok=True, parents=True) | self.build_dir.mkdir(exist_ok=True, parents=True) | ||||
return self.run(['sdist', 'create', self.project_dir_arg], | |||||
cwd=self.build_dir) | |||||
return self.run(['sdist', 'create', self.project_dir_arg], cwd=self.build_dir) | |||||
def sdist_export(self) -> subprocess.CompletedProcess: | def sdist_export(self) -> subprocess.CompletedProcess: | ||||
return self.run([ | return self.run([ | ||||
@property | @property | ||||
def default_builtin_toolchain(self) -> str: | def default_builtin_toolchain(self) -> str: | ||||
if os.name == 'posix': | if os.name == 'posix': | ||||
return ':c++17:gcc-9' | |||||
return str(Path(__file__).parent.joinpath('gcc-9.tc.jsonc')) | |||||
elif os.name == 'nt': | elif os.name == 'nt': | ||||
return ':c++17:msvc' | |||||
return str(Path(__file__).parent.joinpath('msvc.tc.jsonc')) | |||||
else: | else: | ||||
raise RuntimeError( | |||||
f'No default builtin toolchain defined for tests on platform "{os.name}"' | |||||
) | |||||
raise RuntimeError(f'No default builtin toolchain defined for tests on platform "{os.name}"') | |||||
@property | @property | ||||
def exe_suffix(self) -> str: | def exe_suffix(self) -> str: | ||||
elif os.name == 'nt': | elif os.name == 'nt': | ||||
return '.exe' | return '.exe' | ||||
else: | else: | ||||
raise RuntimeError( | |||||
f'We don\'t know the executable suffix for the platform "{os.name}"' | |||||
) | |||||
raise RuntimeError(f'We don\'t know the executable suffix for the platform "{os.name}"') | |||||
def catalog_create(self) -> subprocess.CompletedProcess: | def catalog_create(self) -> subprocess.CompletedProcess: | ||||
self.scratch_dir.mkdir(parents=True, exist_ok=True) | self.scratch_dir.mkdir(parents=True, exist_ok=True) | ||||
return self.run( | |||||
['catalog', 'create', f'--catalog={self.catalog_path}'], | |||||
cwd=self.test_dir) | |||||
return self.run(['catalog', 'create', f'--catalog={self.catalog_path}'], cwd=self.test_dir) | |||||
def catalog_import(self, json_path: Path) -> subprocess.CompletedProcess: | def catalog_import(self, json_path: Path) -> subprocess.CompletedProcess: | ||||
self.scratch_dir.mkdir(parents=True, exist_ok=True) | self.scratch_dir.mkdir(parents=True, exist_ok=True) | ||||
req, | req, | ||||
]) | ]) | ||||
def set_contents(self, path: Union[str, Path], | |||||
content: bytes) -> ContextManager[Path]: | |||||
def set_contents(self, path: Union[str, Path], content: bytes) -> ContextManager[Path]: | |||||
return fileutil.set_contents(self.source_root / path, content) | return fileutil.set_contents(self.source_root / path, content) | ||||
def dds_fixture_conf(*argsets: DDSFixtureParams): | def dds_fixture_conf(*argsets: DDSFixtureParams): | ||||
args = list(argsets) | args = list(argsets) | ||||
return pytest.mark.parametrize( | |||||
'dds', args, indirect=True, ids=[p.ident for p in args]) | |||||
return pytest.mark.parametrize('dds', args, indirect=True, ids=[p.ident for p in args]) | |||||
def dds_fixture_conf_1(subdir: Union[Path, str]): | def dds_fixture_conf_1(subdir: Union[Path, str]): |
{ | |||||
"compiler_id": "gnu", | |||||
"c_compiler": "gcc-9", | |||||
"cxx_compiler": "g++-9", | |||||
"cxx_version": "c++17", | |||||
"cxx_flags": [ | |||||
"-fconcepts" | |||||
] | |||||
} |
{ | |||||
"$schema": "../res/toolchain-schema.json", | |||||
"compiler_id": "msvc" | |||||
} |
f'Unknown "depends" object from json file: {depends!r}') | f'Unknown "depends" object from json file: {depends!r}') | ||||
remote = Git(url=clone_url, ref=tag['name']) | remote = Git(url=clone_url, ref=tag['name']) | ||||
return Version( | |||||
version, description=desc, depends=list(pairs), remote=remote) | |||||
return Version(version, | |||||
description=desc, | |||||
depends=list(pairs), | |||||
remote=remote) | |||||
def github_package(name: str, repo: str, want_tags: Iterable[str]) -> Package: | def github_package(name: str, repo: str, want_tags: Iterable[str]) -> Package: | ||||
tag_items = (t for t in avail_tags if t['name'] in want_tags) | tag_items = (t for t in avail_tags if t['name'] in want_tags) | ||||
versions = HTTP_POOL.map( | versions = HTTP_POOL.map( | ||||
lambda tag: _version_for_github_tag(name, desc, repo_data['clone_url'], tag), | |||||
tag_items) | |||||
lambda tag: _version_for_github_tag(name, desc, repo_data['clone_url'], | |||||
tag), tag_items) | |||||
return Package(name, list(versions)) | return Package(name, list(versions)) | ||||
*, | *, | ||||
tag_fmt: str = '{}') -> Package: | tag_fmt: str = '{}') -> Package: | ||||
return Package(name, [ | return Package(name, [ | ||||
Version( | |||||
ver.version, | |||||
description=description, | |||||
remote=Git( | |||||
git_url, tag_fmt.format(ver.version), auto_lib=auto_lib), | |||||
depends=ver.depends) for ver in versions | |||||
Version(ver.version, | |||||
description=description, | |||||
remote=Git( | |||||
git_url, tag_fmt.format(ver.version), auto_lib=auto_lib), | |||||
depends=ver.depends) for ver in versions | |||||
]) | ]) | ||||
transforms: Sequence[FSTransform] = (), | transforms: Sequence[FSTransform] = (), | ||||
description='(No description was provided)') -> Package: | description='(No description was provided)') -> Package: | ||||
return Package(name, [ | return Package(name, [ | ||||
Version( | |||||
ver, | |||||
description='\n'.join(textwrap.wrap(description)), | |||||
remote=Git( | |||||
url=git_url, | |||||
ref=tag_fmt.format(ver), | |||||
auto_lib=auto_lib, | |||||
transforms=transforms)) for ver in versions | |||||
Version(ver, | |||||
description='\n'.join(textwrap.wrap(description)), | |||||
remote=Git(url=git_url, | |||||
ref=tag_fmt.format(ver), | |||||
auto_lib=auto_lib, | |||||
transforms=transforms)) for ver in versions | |||||
]) | ]) | ||||
['0.2.3', '0.3.0', '0.4.0', '0.4.1']), | ['0.2.3', '0.3.0', '0.4.0', '0.4.1']), | ||||
github_package('neo-fun', 'vector-of-bool/neo-fun', [ | github_package('neo-fun', 'vector-of-bool/neo-fun', [ | ||||
'0.1.1', '0.2.0', '0.2.1', '0.3.0', '0.3.1', '0.3.2', '0.4.0', '0.4.1', | '0.1.1', '0.2.0', '0.2.1', '0.3.0', '0.3.1', '0.3.2', '0.4.0', '0.4.1', | ||||
'0.4.2', '0.5.0', '0.5.1', '0.5.2', '0.5.3', '0.5.4', '0.5.5', | |||||
'0.4.2', '0.5.0', '0.5.1', '0.5.2', '0.5.3', '0.5.4', '0.5.5', '0.6.0', | |||||
]), | ]), | ||||
github_package('neo-io', 'vector-of-bool/neo-io', ['0.1.0']), | |||||
github_package('neo-io', 'vector-of-bool/neo-io', ['0.1.0', '0.1.1']), | |||||
github_package('neo-http', 'vector-of-bool/neo-http', ['0.1.0']), | github_package('neo-http', 'vector-of-bool/neo-http', ['0.1.0']), | ||||
github_package('neo-concepts', 'vector-of-bool/neo-concepts', ( | github_package('neo-concepts', 'vector-of-bool/neo-concepts', ( | ||||
'0.2.2', | '0.2.2', | ||||
Path('catalog.json').write_text(json_str) | Path('catalog.json').write_text(json_str) | ||||
Path('catalog.old.json').write_text( | Path('catalog.old.json').write_text( | ||||
json.dumps(old_data, indent=2, sort_keys=True)) | json.dumps(old_data, indent=2, sort_keys=True)) | ||||
cpp_template = textwrap.dedent(r''' | |||||
#include <dds/catalog/package_info.hpp> | |||||
#include <dds/catalog/init_catalog.hpp> | |||||
#include <dds/catalog/import.hpp> | |||||
#include <neo/gzip.hpp> | |||||
#include <neo/transform_io.hpp> | |||||
#include <neo/string_io.hpp> | |||||
#include <neo/inflate.hpp> | |||||
/** | |||||
* The following array of integers is generated and contains gzip-compressed | |||||
* JSON encoded initial catalog. MSVC can't handle string literals over | |||||
* 64k large, so we have to resort to using a regular char array: | |||||
*/ | |||||
static constexpr const unsigned char INIT_PACKAGES_CONTENT[] = { | |||||
@JSON@ | |||||
}; | |||||
const std::vector<dds::package_info>& | |||||
dds::init_catalog_packages() noexcept { | |||||
using std::nullopt; | |||||
static auto pkgs = []{ | |||||
using namespace neo; | |||||
string_dynbuf_io str_out; | |||||
buffer_copy(str_out, | |||||
buffer_transform_source{ | |||||
buffers_consumer(as_buffer(INIT_PACKAGES_CONTENT)), | |||||
gzip_decompressor{inflate_decompressor{}}}, | |||||
@JSON_LEN@); | |||||
return dds::parse_packages_json(str_out.read_area_view()); | |||||
}(); | |||||
return pkgs; | |||||
} | |||||
''') | |||||
json_small = json.dumps(data, sort_keys=True) | |||||
json_compr = gzip.compress(json_small.encode('utf-8'), compresslevel=9) | |||||
json_small_arr = ','.join(str(c) for c in json_compr) | |||||
cpp_content = cpp_template.replace('@JSON@', json_small_arr).replace( | |||||
'@JSON_LEN@', str(len(json_small))) | |||||
Path('src/dds/catalog/init_catalog.cpp').write_text(cpp_content) |