mirror of
https://github.com/capnproto/pycapnp.git
synced 2025-03-04 08:24:43 +01:00
Updating to capnproto v0.8.0
- Includes some test stabilization - Fixes manylinux2010 build issues (linker flag order due to old gcc) - More rigorous python setup.py clean - Requires capnproto v0.8.0 or greater - Including system libcapnp include path for import (e.g. import stream_capnp) - Bundle libcapnp .capnp files when not using system libcapnp - Removing more distutils usage. Now using pkg-config to determine the system version of libcapnp (mainly for Linux, but should work on macOS with brew) - Removed dead code Resolves issues #215 #216 #217 Lots of fixes for Issue #218 (all sorts of retry methods needed for GitHub Actions)
This commit is contained in:
parent
7bfd5b962b
commit
255119b838
28 changed files with 206 additions and 834 deletions
2
.github/workflows/manylinux2010.yml
vendored
2
.github/workflows/manylinux2010.yml
vendored
|
@ -24,6 +24,8 @@ jobs:
|
|||
/opt/python/${{ matrix.python-version }}/bin/python -m pip install -r requirements.txt
|
||||
/opt/python/${{ matrix.python-version }}/bin/python -m pip install auditwheel
|
||||
- name: Build pycapnp and install
|
||||
env:
|
||||
LDFLAGS: -Wl,--no-as-needed -lrt
|
||||
run: |
|
||||
/opt/python/${{ matrix.python-version }}/bin/python setup.py build
|
||||
- name: Packaging
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -8,6 +8,8 @@
|
|||
*.egg-info
|
||||
dist
|
||||
build
|
||||
build32
|
||||
build64
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
|
@ -47,3 +49,6 @@ capnp/lib/capnp.h
|
|||
bundled/
|
||||
example
|
||||
*.iml
|
||||
|
||||
# capnp files
|
||||
*.capnp
|
||||
|
|
14
README.md
14
README.md
|
@ -20,7 +20,7 @@
|
|||
- ninja (macOS + Linux)
|
||||
- Visual Studio 2017+
|
||||
|
||||
* capnproto-0.7.0
|
||||
* capnproto-0.8.0
|
||||
- Not necessary if using bundled capnproto
|
||||
|
||||
32-bit Linux requires that capnproto be compiled with `-fPIC`. This is usually set correctly unless you are compiling canproto yourself. This is also called `-DCMAKE_POSITION_INDEPENDENT_CODE=1` for cmake.
|
||||
|
@ -55,6 +55,18 @@ To force bundled python:
|
|||
pip install --install-option "--force-bundled-libcapnp" .
|
||||
```
|
||||
|
||||
Slightly more prompt error messages using distutils rather than pip.
|
||||
|
||||
```bash
|
||||
python setup.py install --force-bundled-libcapnp
|
||||
```
|
||||
|
||||
The bundling system isn't that smart so it might be necessary to clean up the bundled build when changing versions:
|
||||
|
||||
```bash
|
||||
python setup.py clean
|
||||
```
|
||||
|
||||
|
||||
## Python Versions
|
||||
|
||||
|
|
|
@ -5,9 +5,5 @@ Largely adapted from h5py
|
|||
# flake8: noqa F401 F403
|
||||
|
||||
from .msg import *
|
||||
from .config import *
|
||||
from .detect import *
|
||||
from .bundle import *
|
||||
from .misc import *
|
||||
from .patch import *
|
||||
from .build import *
|
||||
|
|
|
@ -22,7 +22,9 @@ def build_libcapnp(bundle_dir, build_dir):
|
|||
os.mkdir(tmp_dir)
|
||||
|
||||
cxxflags = os.environ.get('CXXFLAGS', None)
|
||||
ldflags = os.environ.get('LDFLAGS', None)
|
||||
os.environ['CXXFLAGS'] = (cxxflags or '') + ' -O2 -DNDEBUG'
|
||||
os.environ['LDFLAGS'] = (ldflags or '')
|
||||
|
||||
# Enable ninja for compilation if available
|
||||
build_type = []
|
||||
|
@ -77,5 +79,9 @@ def build_libcapnp(bundle_dir, build_dir):
|
|||
del os.environ['CXXFLAGS']
|
||||
else:
|
||||
os.environ['CXXFLAGS'] = cxxflags
|
||||
if ldflags is None:
|
||||
del os.environ['LDFLAGS']
|
||||
else:
|
||||
os.environ['LDFLAGS'] = ldflags
|
||||
if returncode != 0:
|
||||
raise RuntimeError('capnproto compilation failed: {}'.format(returncode))
|
||||
|
|
|
@ -27,7 +27,7 @@ pjoin = os.path.join
|
|||
#
|
||||
|
||||
|
||||
bundled_version = (0, 7, 0)
|
||||
bundled_version = (0, 8, 0)
|
||||
libcapnp_name = "capnproto-c++-%i.%i.%i.tar.gz" % (bundled_version)
|
||||
libcapnp_url = "https://capnproto.org/" + libcapnp_name
|
||||
|
||||
|
@ -92,14 +92,3 @@ def fetch_libcapnp(savedir, url=None):
|
|||
else:
|
||||
cpp_dir = os.path.join(with_version, 'c++')
|
||||
shutil.move(cpp_dir, dest)
|
||||
|
||||
# XXX (HaaTa): There's a bug in capnproto-0.7.0 on CentOS 6 (or any glibc older than 2.16)
|
||||
# This patches the issue
|
||||
with fileinput.FileInput(os.path.join(dest, 'src', 'kj', 'table.c++'), inplace=True, backup='.bak') as f:
|
||||
for line in f:
|
||||
print(
|
||||
line.replace(
|
||||
'__APPLE__ || __BIONIC__', '__APPLE__ || __BIONIC__ || (defined(__GLIBC__) && (__GLIBC__ == 2) && (__GLIBC_MINOR__ < 16))'
|
||||
),
|
||||
end=''
|
||||
)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
"""Config functions"""
|
||||
#
|
||||
# Copyright (C) PyZMQ Developers
|
||||
#
|
||||
# This file is part of pyzmq, copied and adapted from h5py.
|
||||
# h5py source used under the New BSD license
|
||||
#
|
||||
# h5py: <http://code.google.com/p/h5py/>
|
||||
#
|
||||
# Distributed under the terms of the New BSD License. The full license is in
|
||||
# the file COPYING.BSD, distributed as part of this software.
|
||||
#
|
||||
|
||||
#
|
||||
# Utility functions (adapted from h5py: http://h5py.googlecode.com)
|
||||
#
|
||||
|
||||
|
||||
def v_str(v_tuple):
|
||||
"""turn (2,0,1) into '2.0.1'."""
|
||||
return ".".join(str(x) for x in v_tuple)
|
|
@ -1,154 +0,0 @@
|
|||
"""Detect zmq version"""
|
||||
#
|
||||
# Copyright (C) PyZMQ Developers
|
||||
#
|
||||
# This file is part of pyzmq, copied and adapted from h5py.
|
||||
# h5py source used under the New BSD license
|
||||
#
|
||||
# h5py: <http://code.google.com/p/h5py/>
|
||||
#
|
||||
# Distributed under the terms of the New BSD License. The full license is in
|
||||
# the file COPYING.BSD, distributed as part of this software.
|
||||
#
|
||||
#
|
||||
# Adapted for use in pycapnp from pyzmq. See https://github.com/zeromq/pyzmq
|
||||
# for original project.
|
||||
|
||||
import shutil
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import platform
|
||||
from distutils import ccompiler
|
||||
from distutils.ccompiler import get_default_compiler
|
||||
import tempfile
|
||||
|
||||
from .misc import get_compiler, get_output_error
|
||||
from .patch import patch_lib_paths
|
||||
|
||||
pjoin = os.path.join
|
||||
|
||||
#
|
||||
# Utility functions (adapted from h5py: http://h5py.googlecode.com)
|
||||
#
|
||||
|
||||
|
||||
def test_compilation(cfile, compiler=None, **compiler_attrs):
|
||||
"""Test simple compilation with given settings"""
|
||||
cc = get_compiler(compiler, **compiler_attrs)
|
||||
|
||||
efile, _ = os.path.splitext(cfile)
|
||||
|
||||
cpreargs = lpreargs = []
|
||||
if sys.platform == 'darwin':
|
||||
# use appropriate arch for compiler
|
||||
if platform.architecture()[0] == '32bit':
|
||||
if platform.processor() == 'powerpc':
|
||||
cpu = 'ppc'
|
||||
else:
|
||||
cpu = 'i386'
|
||||
cpreargs = ['-arch', cpu]
|
||||
lpreargs = ['-arch', cpu, '-undefined', 'dynamic_lookup']
|
||||
else:
|
||||
# allow for missing UB arch, since it will still work:
|
||||
lpreargs = ['-undefined', 'dynamic_lookup']
|
||||
if sys.platform == 'sunos5':
|
||||
if platform.architecture()[0] == '32bit':
|
||||
lpreargs = ['-m32']
|
||||
else:
|
||||
lpreargs = ['-m64']
|
||||
extra_compile_args = compiler_attrs.get('extra_compile_args', [])
|
||||
if os.name != 'nt':
|
||||
extra_compile_args += ['--std=c++14']
|
||||
extra_link_args = compiler_attrs.get('extra_link_args', [])
|
||||
if cc.compiler_type == 'msvc':
|
||||
extra_link_args += ['/MANIFEST']
|
||||
|
||||
objs = cc.compile([cfile], extra_preargs=cpreargs, extra_postargs=extra_compile_args)
|
||||
cc.link_executable(objs, efile, extra_preargs=lpreargs, extra_postargs=extra_link_args)
|
||||
return efile
|
||||
|
||||
|
||||
def detect_version(basedir, compiler=None, **compiler_attrs):
|
||||
"""Compile, link & execute a test program, in empty directory `basedir`.
|
||||
|
||||
The C compiler will be updated with any keywords given via setattr.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
basedir : path
|
||||
The location where the test program will be compiled and run
|
||||
compiler : str
|
||||
The distutils compiler key (e.g. 'unix', 'msvc', or 'mingw32')
|
||||
**compiler_attrs : dict
|
||||
Any extra compiler attributes, which will be set via ``setattr(cc)``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
A dict of properties for zmq compilation, with the following two keys:
|
||||
|
||||
vers : tuple
|
||||
The ZMQ version as a tuple of ints, e.g. (2,2,0)
|
||||
settings : dict
|
||||
The compiler options used to compile the test function, e.g. `include_dirs`,
|
||||
`library_dirs`, `libs`, etc.
|
||||
"""
|
||||
if compiler is None:
|
||||
compiler = get_default_compiler()
|
||||
cfile = pjoin(basedir, 'vers.cpp')
|
||||
shutil.copy(pjoin(os.path.dirname(__file__), 'vers.cpp'), cfile)
|
||||
|
||||
# check if we need to link against Realtime Extensions library
|
||||
if sys.platform.startswith('linux'):
|
||||
cc = ccompiler.new_compiler(compiler=compiler)
|
||||
cc.output_dir = basedir
|
||||
if not cc.has_function('timer_create'):
|
||||
if 'libraries' not in compiler_attrs:
|
||||
compiler_attrs['libraries'] = []
|
||||
compiler_attrs['libraries'].append('rt')
|
||||
|
||||
cc = get_compiler(compiler=compiler, **compiler_attrs)
|
||||
efile = test_compilation(cfile, compiler=cc)
|
||||
patch_lib_paths(efile, cc.library_dirs)
|
||||
|
||||
rc, so, se = get_output_error([efile])
|
||||
if rc:
|
||||
msg = "Error running version detection script:\n%s\n%s" % (so, se)
|
||||
logging.error(msg)
|
||||
raise IOError(msg)
|
||||
|
||||
handlers = {'vers': lambda val: tuple(int(v) for v in val.split('.'))}
|
||||
|
||||
props = {}
|
||||
for line in (x for x in so.split('\n') if x):
|
||||
key, val = line.split(':')
|
||||
props[key] = handlers[key](val)
|
||||
|
||||
return props
|
||||
|
||||
|
||||
def test_build(**compiler_attrs):
|
||||
"""do a test build of libcapnp"""
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
|
||||
# line()
|
||||
# info("Configure: Autodetecting Cap'n Proto settings...")
|
||||
# info(" Custom Cap'n Proto dir: %s" % prefix)
|
||||
try:
|
||||
detected = detect_version(tmp_dir, None, **compiler_attrs)
|
||||
finally:
|
||||
erase_dir(tmp_dir)
|
||||
|
||||
# info(" Cap'n Proto version detected: %s" % v_str(detected['vers']))
|
||||
|
||||
return detected
|
||||
|
||||
|
||||
def erase_dir(path):
|
||||
"""Erase directory"""
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except Exception:
|
||||
pass
|
|
@ -1,64 +0,0 @@
|
|||
"""misc build utility functions"""
|
||||
|
||||
# Copyright (c) PyZMQ Developers
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
import logging
|
||||
from distutils import ccompiler
|
||||
from distutils.sysconfig import customize_compiler
|
||||
from pipes import quote
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
pjoin = os.path.join
|
||||
|
||||
|
||||
def customize_mingw(cc):
|
||||
"""customize mingw"""
|
||||
# strip -mno-cygwin from mingw32 (Python Issue #12641)
|
||||
for cmd in [cc.compiler, cc.compiler_cxx, cc.compiler_so, cc.linker_exe, cc.linker_so]:
|
||||
if '-mno-cygwin' in cmd:
|
||||
cmd.remove('-mno-cygwin')
|
||||
|
||||
# remove problematic msvcr90
|
||||
if 'msvcr90' in cc.dll_libraries:
|
||||
cc.dll_libraries.remove('msvcr90')
|
||||
|
||||
|
||||
def customize_msvc(cc):
|
||||
pass
|
||||
|
||||
|
||||
def get_compiler(compiler, **compiler_attrs):
|
||||
"""get and customize a compiler"""
|
||||
if compiler is None or isinstance(compiler, str):
|
||||
cc = ccompiler.new_compiler(compiler=compiler)
|
||||
customize_compiler(cc)
|
||||
if cc.compiler_type == 'mingw32':
|
||||
customize_mingw(cc)
|
||||
elif cc.compiler_type == 'msvc':
|
||||
customize_msvc(cc)
|
||||
else:
|
||||
cc = compiler
|
||||
|
||||
for name, val in compiler_attrs.items():
|
||||
setattr(cc, name, val)
|
||||
|
||||
return cc
|
||||
|
||||
|
||||
def get_output_error(cmd):
|
||||
"""Return the exit status, stdout, stderr of a command"""
|
||||
if not isinstance(cmd, list):
|
||||
cmd = [cmd]
|
||||
logging.debug("Running: %s", ' '.join(map(quote, cmd)))
|
||||
try:
|
||||
result = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||
except IOError as e:
|
||||
return -1, '', 'Failed to run %r: %r' % (cmd, e)
|
||||
so, se = result.communicate()
|
||||
# unicode:
|
||||
so = so.decode('utf8', 'replace')
|
||||
se = se.decode('utf8', 'replace')
|
||||
|
||||
return result.returncode, so, se
|
|
@ -1,67 +0,0 @@
|
|||
"""utils for patching libraries"""
|
||||
|
||||
# Copyright (c) PyZMQ Developers.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
from .misc import get_output_error
|
||||
|
||||
pjoin = os.path.join
|
||||
|
||||
# LIB_PAT from delocate
|
||||
LIB_PAT = re.compile(
|
||||
r"\s*(.*) \(compatibility version (\d+\.\d+\.\d+), current version (\d+\.\d+\.\d+)\)"
|
||||
)
|
||||
|
||||
|
||||
def _get_libs(fname):
|
||||
rc, so, se = get_output_error(['otool', '-L', fname])
|
||||
if rc:
|
||||
logging.error("otool -L %s failed: %r", fname, se)
|
||||
return
|
||||
for line in so.splitlines()[1:]:
|
||||
m = LIB_PAT.match(line)
|
||||
if m:
|
||||
yield m.group(1)
|
||||
|
||||
|
||||
def _find_library(lib, path):
|
||||
"""Find a library"""
|
||||
for d in path[::-1]:
|
||||
real_lib = os.path.join(d, lib)
|
||||
if os.path.exists(real_lib):
|
||||
return real_lib
|
||||
return None
|
||||
|
||||
|
||||
def _install_name_change(fname, lib, real_lib):
|
||||
rc, so, se = get_output_error(['install_name_tool', '-change', lib, real_lib, fname])
|
||||
if rc:
|
||||
logging.error("Couldn't update load path: %s", se)
|
||||
|
||||
|
||||
def patch_lib_paths(fname, library_dirs):
|
||||
"""Load any weakly-defined libraries from their real location
|
||||
|
||||
(only on OS X)
|
||||
|
||||
- Find libraries with `otool -L`
|
||||
- Update with `install_name_tool -change`
|
||||
"""
|
||||
if sys.platform != 'darwin':
|
||||
return
|
||||
|
||||
libs = _get_libs(fname)
|
||||
for lib in libs:
|
||||
if not lib.startswith(('@', '/')):
|
||||
real_lib = _find_library(lib, library_dirs)
|
||||
if real_lib:
|
||||
_install_name_change(fname, lib, real_lib)
|
||||
|
||||
|
||||
__all__ = ['patch_lib_paths']
|
|
@ -1,9 +0,0 @@
|
|||
// check libcapnp version
|
||||
|
||||
#include <stdio.h>
|
||||
#include "capnp/common.h"
|
||||
|
||||
int main(int argc, char **argv){
|
||||
fprintf(stdout, "vers: %d.%d.%d\n", CAPNP_VERSION_MAJOR, CAPNP_VERSION_MINOR, CAPNP_VERSION_MICRO);
|
||||
return 0;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
||||
# Licensed under the MIT License:
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
@0xbdf87d7bb8304e81;
|
||||
$namespace("capnp::annotations");
|
||||
|
||||
annotation namespace(file): Text;
|
||||
annotation name(field, enumerant, struct, enum, interface, method, param, group, union): Text;
|
|
@ -5,4 +5,4 @@
|
|||
|
||||
#include "capnp/dynamic.h"
|
||||
|
||||
static_assert(CAPNP_VERSION >= 7000, "Version of Cap'n Proto C++ Library is too old. Please upgrade to a version >= 0.7 and then re-install this python library");
|
||||
static_assert(CAPNP_VERSION >= 8000, "Version of Cap'n Proto C++ Library is too old. Please upgrade to a version >= 0.8 and then re-install this python library");
|
||||
|
|
|
@ -4070,6 +4070,17 @@ def add_import_hook(additional_paths=[]):
|
|||
if _importer is not None:
|
||||
remove_import_hook()
|
||||
|
||||
# Automatically include the system and built-in capnp paths
|
||||
# Highest priority at position 0
|
||||
extra_paths = [
|
||||
_os.path.join(_os.path.dirname(__file__), '..'), # Built-in (only used if bundled)
|
||||
'/usr/local/include/capnp', # Common macOS brew location
|
||||
'/usr/include/capnp', # Common posix location
|
||||
]
|
||||
for path in extra_paths:
|
||||
if _os.path.isdir(path):
|
||||
additional_paths.append(path)
|
||||
|
||||
_importer = _Importer(additional_paths)
|
||||
_sys.meta_path.append(_importer)
|
||||
|
||||
|
|
|
@ -1,383 +0,0 @@
|
|||
# Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
using Cxx = import "c++.capnp";
|
||||
|
||||
@0xa93fc509624c72d9;
|
||||
$Cxx.namespace("capnp::schema");
|
||||
|
||||
using Id = UInt64;
|
||||
# The globally-unique ID of a file, type, or annotation.
|
||||
|
||||
struct Node {
|
||||
id @0 :Id;
|
||||
|
||||
displayName @1 :Text;
|
||||
# Name to present to humans to identify this Node. You should not attempt to parse this. Its
|
||||
# format could change. It is not guaranteed to be unique.
|
||||
#
|
||||
# (On Zooko's triangle, this is the node's nickname.)
|
||||
|
||||
displayNamePrefixLength @2 :UInt32;
|
||||
# If you want a shorter version of `displayName` (just naming this node, without its surrounding
|
||||
# scope), chop off this many characters from the beginning of `displayName`.
|
||||
|
||||
scopeId @3 :Id;
|
||||
# ID of the lexical parent node. Typically, the scope node will have a NestedNode pointing back
|
||||
# at this node, but robust code should avoid relying on this (and, in fact, group nodes are not
|
||||
# listed in the outer struct's nestedNodes, since they are listed in the fields). `scopeId` is
|
||||
# zero if the node has no parent, which is normally only the case with files, but should be
|
||||
# allowed for any kind of node (in order to make runtime type generation easier).
|
||||
|
||||
nestedNodes @4 :List(NestedNode);
|
||||
# List of nodes nested within this node, along with the names under which they were declared.
|
||||
|
||||
struct NestedNode {
|
||||
name @0 :Text;
|
||||
# Unqualified symbol name. Unlike Node.name, this *can* be used programmatically.
|
||||
#
|
||||
# (On Zooko's triangle, this is the node's petname according to its parent scope.)
|
||||
|
||||
id @1 :Id;
|
||||
# ID of the nested node. Typically, the target node's scopeId points back to this node, but
|
||||
# robust code should avoid relying on this.
|
||||
}
|
||||
|
||||
annotations @5 :List(Annotation);
|
||||
# Annotations applied to this node.
|
||||
|
||||
union {
|
||||
# Info specific to each kind of node.
|
||||
|
||||
file @6 :Void;
|
||||
|
||||
struct :group {
|
||||
dataWordCount @7 :UInt16;
|
||||
# Size of the data section, in words.
|
||||
|
||||
pointerCount @8 :UInt16;
|
||||
# Size of the pointer section, in pointers (which are one word each).
|
||||
|
||||
preferredListEncoding @9 :ElementSize;
|
||||
# The preferred element size to use when encoding a list of this struct. If this is anything
|
||||
# other than `inlineComposite` then the struct is one word or less in size and is a candidate
|
||||
# for list packing optimization.
|
||||
|
||||
isGroup @10 :Bool;
|
||||
# If true, then this "struct" node is actually not an independent node, but merely represents
|
||||
# some named union or group within a particular parent struct. This node's scopeId refers
|
||||
# to the parent struct, which may itself be a union/group in yet another struct.
|
||||
#
|
||||
# All group nodes share the same dataWordCount and pointerCount as the top-level
|
||||
# struct, and their fields live in the same ordinal and offset spaces as all other fields in
|
||||
# the struct.
|
||||
#
|
||||
# Note that a named union is considered a special kind of group -- in fact, a named union
|
||||
# is exactly equivalent to a group that contains nothing but an unnamed union.
|
||||
|
||||
discriminantCount @11 :UInt16;
|
||||
# Number of fields in this struct which are members of an anonymous union, and thus may
|
||||
# overlap. If this is non-zero, then a 16-bit discriminant is present indicating which
|
||||
# of the overlapping fields is active. This can never be 1 -- if it is non-zero, it must be
|
||||
# two or more.
|
||||
#
|
||||
# Note that the fields of an unnamed union are considered fields of the scope containing the
|
||||
# union -- an unnamed union is not its own group. So, a top-level struct may contain a
|
||||
# non-zero discriminant count. Named unions, on the other hand, are equivalent to groups
|
||||
# containing unnamed unions. So, a named union has its own independent schema node, with
|
||||
# `isGroup` = true.
|
||||
|
||||
discriminantOffset @12 :UInt32;
|
||||
# If `discriminantCount` is non-zero, this is the offset of the union discriminant, in
|
||||
# multiples of 16 bits.
|
||||
|
||||
fields @13 :List(Field);
|
||||
# Fields defined within this scope (either the struct's top-level fields, or the fields of
|
||||
# a particular group; see `isGroup`).
|
||||
#
|
||||
# The fields are sorted by ordinal number, but note that because groups share the same
|
||||
# ordinal space, the field's index in this list is not necessarily exactly its ordinal.
|
||||
# On the other hand, the field's position in this list does remain the same even as the
|
||||
# protocol evolves, since it is not possible to insert or remove an earlier ordinal.
|
||||
# Therefore, for most use cases, if you want to identify a field by number, it may make the
|
||||
# most sense to use the field's index in this list rather than its ordinal.
|
||||
}
|
||||
|
||||
enum :group {
|
||||
enumerants@14 :List(Enumerant);
|
||||
# Enumerants ordered by numeric value (ordinal).
|
||||
}
|
||||
|
||||
interface :group {
|
||||
methods @15 :List(Method);
|
||||
# Methods ordered by ordinal.
|
||||
|
||||
extends @31 :List(Id);
|
||||
# Superclasses of this interface.
|
||||
}
|
||||
|
||||
const :group {
|
||||
type @16 :Type;
|
||||
value @17 :Value;
|
||||
}
|
||||
|
||||
annotation :group {
|
||||
type @18 :Type;
|
||||
|
||||
targetsFile @19 :Bool;
|
||||
targetsConst @20 :Bool;
|
||||
targetsEnum @21 :Bool;
|
||||
targetsEnumerant @22 :Bool;
|
||||
targetsStruct @23 :Bool;
|
||||
targetsField @24 :Bool;
|
||||
targetsUnion @25 :Bool;
|
||||
targetsGroup @26 :Bool;
|
||||
targetsInterface @27 :Bool;
|
||||
targetsMethod @28 :Bool;
|
||||
targetsParam @29 :Bool;
|
||||
targetsAnnotation @30 :Bool;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Field {
|
||||
# Schema for a field of a struct.
|
||||
|
||||
name @0 :Text;
|
||||
|
||||
codeOrder @1 :UInt16;
|
||||
# Indicates where this member appeared in the code, relative to other members.
|
||||
# Code ordering may have semantic relevance -- programmers tend to place related fields
|
||||
# together. So, using code ordering makes sense in human-readable formats where ordering is
|
||||
# otherwise irrelevant, like JSON. The values of codeOrder are tightly-packed, so the maximum
|
||||
# value is count(members) - 1. Fields that are members of a union are only ordered relative to
|
||||
# the other members of that union, so the maximum value there is count(union.members).
|
||||
|
||||
annotations @2 :List(Annotation);
|
||||
|
||||
const noDiscriminant :UInt16 = 0xffff;
|
||||
|
||||
discriminantValue @3 :UInt16 = Field.noDiscriminant;
|
||||
# If the field is in a union, this is the value which the union's discriminant should take when
|
||||
# the field is active. If the field is not in a union, this is 0xffff.
|
||||
|
||||
union {
|
||||
slot :group {
|
||||
# A regular, non-group, non-fixed-list field.
|
||||
|
||||
offset @4 :UInt32;
|
||||
# Offset, in units of the field's size, from the beginning of the section in which the field
|
||||
# resides. E.g. for a UInt32 field, multiply this by 4 to get the byte offset from the
|
||||
# beginning of the data section.
|
||||
|
||||
type @5 :Type;
|
||||
defaultValue @6 :Value;
|
||||
|
||||
hadExplicitDefault @10 :Bool;
|
||||
# Whether the default value was specified explicitly. Non-explicit default values are always
|
||||
# zero or empty values. Usually, whether the default value was explicit shouldn't matter.
|
||||
# The main use case for this flag is for structs representing method parameters:
|
||||
# explicitly-defaulted parameters may be allowed to be omitted when calling the method.
|
||||
}
|
||||
|
||||
group :group {
|
||||
# A group.
|
||||
|
||||
typeId @7 :Id;
|
||||
# The ID of the group's node.
|
||||
}
|
||||
}
|
||||
|
||||
ordinal :union {
|
||||
implicit @8 :Void;
|
||||
explicit @9 :UInt16;
|
||||
# The original ordinal number given to the field. You probably should NOT use this; if you need
|
||||
# a numeric identifier for a field, use its position within the field array for its scope.
|
||||
# The ordinal is given here mainly just so that the original schema text can be reproduced given
|
||||
# the compiled version -- i.e. so that `capnp compile -ocapnp` can do its job.
|
||||
}
|
||||
}
|
||||
|
||||
struct Enumerant {
|
||||
# Schema for member of an enum.
|
||||
|
||||
name @0 :Text;
|
||||
|
||||
codeOrder @1 :UInt16;
|
||||
# Specifies order in which the enumerants were declared in the code.
|
||||
# Like Struct.Field.codeOrder.
|
||||
|
||||
annotations @2 :List(Annotation);
|
||||
}
|
||||
|
||||
struct Method {
|
||||
# Schema for method of an interface.
|
||||
|
||||
name @0 :Text;
|
||||
|
||||
codeOrder @1 :UInt16;
|
||||
# Specifies order in which the methods were declared in the code.
|
||||
# Like Struct.Field.codeOrder.
|
||||
|
||||
paramStructType @2 :Id;
|
||||
# ID of the parameter struct type. If a named parameter list was specified in the method
|
||||
# declaration (rather than a single struct parameter type) then a corresponding struct type is
|
||||
# auto-generated. Such an auto-generated type will not be listed in the interface's
|
||||
# `nestedNodes` and its `scopeId` will be zero -- it is completely detached from the namespace.
|
||||
|
||||
resultStructType @3 :Id;
|
||||
# ID of the return struct type; similar to `paramStructType`.
|
||||
|
||||
annotations @4 :List(Annotation);
|
||||
}
|
||||
|
||||
struct Type {
|
||||
# Represents a type expression.
|
||||
|
||||
union {
|
||||
# The ordinals intentionally match those of Value.
|
||||
|
||||
void @0 :Void;
|
||||
bool @1 :Void;
|
||||
int8 @2 :Void;
|
||||
int16 @3 :Void;
|
||||
int32 @4 :Void;
|
||||
int64 @5 :Void;
|
||||
uint8 @6 :Void;
|
||||
uint16 @7 :Void;
|
||||
uint32 @8 :Void;
|
||||
uint64 @9 :Void;
|
||||
float32 @10 :Void;
|
||||
float64 @11 :Void;
|
||||
text @12 :Void;
|
||||
data @13 :Void;
|
||||
|
||||
list :group {
|
||||
elementType @14 :Type;
|
||||
}
|
||||
|
||||
enum :group {
|
||||
typeId @15 :Id;
|
||||
}
|
||||
struct :group {
|
||||
typeId @16 :Id;
|
||||
}
|
||||
interface :group {
|
||||
typeId @17 :Id;
|
||||
}
|
||||
|
||||
anyPointer @18 :Void;
|
||||
}
|
||||
}
|
||||
|
||||
struct Value {
|
||||
# Represents a value, e.g. a field default value, constant value, or annotation value.
|
||||
|
||||
union {
|
||||
# The ordinals intentionally match those of Type.
|
||||
|
||||
void @0 :Void;
|
||||
bool @1 :Bool;
|
||||
int8 @2 :Int8;
|
||||
int16 @3 :Int16;
|
||||
int32 @4 :Int32;
|
||||
int64 @5 :Int64;
|
||||
uint8 @6 :UInt8;
|
||||
uint16 @7 :UInt16;
|
||||
uint32 @8 :UInt32;
|
||||
uint64 @9 :UInt64;
|
||||
float32 @10 :Float32;
|
||||
float64 @11 :Float64;
|
||||
text @12 :Text;
|
||||
data @13 :Data;
|
||||
|
||||
list @14 :AnyPointer;
|
||||
|
||||
enum @15 :UInt16;
|
||||
struct @16 :AnyPointer;
|
||||
|
||||
interface @17 :Void;
|
||||
# The only interface value that can be represented statically is "null", whose methods always
|
||||
# throw exceptions.
|
||||
|
||||
anyPointer @18 :AnyPointer;
|
||||
}
|
||||
}
|
||||
|
||||
struct Annotation {
|
||||
# Describes an annotation applied to a declaration. Note AnnotationNode describes the
|
||||
# annotation's declaration, while this describes a use of the annotation.
|
||||
|
||||
id @0 :Id;
|
||||
# ID of the annotation node.
|
||||
|
||||
value @1 :Value;
|
||||
}
|
||||
|
||||
enum ElementSize {
|
||||
# Possible element sizes for encoded lists. These correspond exactly to the possible values of
|
||||
# the 3-bit element size component of a list pointer.
|
||||
|
||||
empty @0; # aka "void", but that's a keyword.
|
||||
bit @1;
|
||||
byte @2;
|
||||
twoBytes @3;
|
||||
fourBytes @4;
|
||||
eightBytes @5;
|
||||
pointer @6;
|
||||
inlineComposite @7;
|
||||
}
|
||||
|
||||
struct CodeGeneratorRequest {
|
||||
nodes @0 :List(Node);
|
||||
# All nodes parsed by the compiler, including for the files on the command line and their
|
||||
# imports.
|
||||
|
||||
requestedFiles @1 :List(RequestedFile);
|
||||
# Files which were listed on the command line.
|
||||
|
||||
struct RequestedFile {
|
||||
id @0 :Id;
|
||||
# ID of the file.
|
||||
|
||||
filename @1 :Text;
|
||||
# Name of the file as it appeared on the command-line (minus the src-prefix). You may use
|
||||
# this to decide where to write the output.
|
||||
|
||||
imports @2 :List(Import);
|
||||
# List of all imported paths seen in this file.
|
||||
|
||||
struct Import {
|
||||
id @0 :Id;
|
||||
# ID of the imported file.
|
||||
|
||||
name @1 :Text;
|
||||
# Name which *this* file used to refer to the foreign file. This may be a relative name.
|
||||
# This information is provided because it might be useful for code generation, e.g. to
|
||||
# generate #include directives in C++. We don't put this in Node.file because this
|
||||
# information is only meaningful at compile time anyway.
|
||||
#
|
||||
# (On Zooko's triangle, this is the import's petname according to the importing file.)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ async def main(host):
|
|||
print("Try IPv4")
|
||||
reader, writer = await asyncio.open_connection(
|
||||
addr, port,
|
||||
family=socket.AF_INET
|
||||
)
|
||||
except Exception:
|
||||
print("Try IPv6")
|
||||
|
|
|
@ -214,6 +214,7 @@ async def main():
|
|||
server = await asyncio.start_server(
|
||||
new_connection,
|
||||
addr, port,
|
||||
family=socket.AF_INET
|
||||
)
|
||||
except Exception:
|
||||
print("Try IPv6")
|
||||
|
|
|
@ -56,6 +56,7 @@ async def main(host):
|
|||
print("Try IPv4")
|
||||
reader, writer = await asyncio.open_connection(
|
||||
addr, port,
|
||||
family=socket.AF_INET
|
||||
)
|
||||
except Exception:
|
||||
print("Try IPv6")
|
||||
|
|
|
@ -86,6 +86,7 @@ async def main(host):
|
|||
reader, writer = await asyncio.open_connection(
|
||||
addr, port,
|
||||
ssl=ctx,
|
||||
family=socket.AF_INET
|
||||
)
|
||||
except OSError:
|
||||
print("Try IPv6")
|
||||
|
|
|
@ -116,6 +116,7 @@ async def main():
|
|||
server = await asyncio.start_server(
|
||||
new_connection,
|
||||
addr, port,
|
||||
family=socket.AF_INET
|
||||
)
|
||||
except Exception:
|
||||
print("Try IPv6")
|
||||
|
|
|
@ -65,6 +65,7 @@ async def main(host):
|
|||
reader, writer = await asyncio.open_connection(
|
||||
addr, port,
|
||||
ssl=ctx,
|
||||
family=socket.AF_INET
|
||||
)
|
||||
except Exception:
|
||||
print("Try IPv6")
|
||||
|
|
|
@ -128,6 +128,7 @@ async def main():
|
|||
new_connection,
|
||||
addr, port,
|
||||
ssl=ctx,
|
||||
family=socket.AF_INET,
|
||||
)
|
||||
except Exception:
|
||||
print("Try IPv6")
|
||||
|
|
|
@ -30,7 +30,7 @@ class StatusSubscriber(thread_capnp.Example.StatusSubscriber.Server):
|
|||
|
||||
|
||||
def start_status_thread(host):
|
||||
client = capnp.TwoPartyClient(host)
|
||||
client = capnp.TwoPartyClient(host, nesting_limit=64)
|
||||
cap = client.bootstrap().cast_as(thread_capnp.Example)
|
||||
|
||||
subscriber = StatusSubscriber()
|
||||
|
@ -39,7 +39,7 @@ def start_status_thread(host):
|
|||
|
||||
|
||||
def main(host):
|
||||
client = capnp.TwoPartyClient(host)
|
||||
client = capnp.TwoPartyClient(host, nesting_limit=64)
|
||||
cap = client.bootstrap().cast_as(thread_capnp.Example)
|
||||
|
||||
status_thread = threading.Thread(target=start_status_thread, args=(host,))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
jinja2
|
||||
cython
|
||||
setuptools
|
||||
pkgconfig
|
||||
pytest
|
||||
tox
|
||||
wheel
|
||||
|
|
40
setup.py
40
setup.py
|
@ -5,18 +5,19 @@ pycapnp distutils setup.py
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import pkgconfig
|
||||
|
||||
from distutils.command.clean import clean as _clean
|
||||
from distutils.errors import CompileError
|
||||
from distutils.extension import Extension
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
from setuptools import setup, find_packages, Extension
|
||||
|
||||
from buildutils import test_build, fetch_libcapnp, build_libcapnp, info
|
||||
from buildutils import fetch_libcapnp, build_libcapnp, info
|
||||
|
||||
_this_dir = os.path.dirname(__file__)
|
||||
|
||||
|
@ -69,12 +70,20 @@ class clean(_clean):
|
|||
'''
|
||||
def run(self):
|
||||
_clean.run(self)
|
||||
for x in [ 'capnp/lib/capnp.cpp', 'capnp/lib/capnp.h', 'capnp/version.py' ]:
|
||||
for x in [
|
||||
os.path.join('capnp', 'lib', 'capnp.cpp'),
|
||||
os.path.join('capnp', 'lib', 'capnp.h'),
|
||||
os.path.join('capnp', 'version.py'),
|
||||
'build',
|
||||
'build32',
|
||||
'build64',
|
||||
'bundled'
|
||||
] + glob.glob(os.path.join('capnp', '*.capnp')):
|
||||
print('removing %s' % x)
|
||||
try:
|
||||
os.remove(x)
|
||||
except OSError:
|
||||
pass
|
||||
shutil.rmtree(x, ignore_errors=True)
|
||||
|
||||
|
||||
# hack to parse commandline arguments
|
||||
|
@ -113,18 +122,20 @@ class build_libcapnp_ext(build_ext_c):
|
|||
need_build = False
|
||||
else:
|
||||
# Try to use capnp executable to find include and lib path
|
||||
capnp_executable = find_executable("capnp")
|
||||
capnp_executable = shutil.which("capnp")
|
||||
if capnp_executable:
|
||||
self.include_dirs += [os.path.join(os.path.dirname(capnp_executable), '..', 'include')]
|
||||
self.library_dirs += [os.path.join(os.path.dirname(capnp_executable), '..', 'lib{}'.format(8 * struct.calcsize("P")))]
|
||||
self.library_dirs += [os.path.join(os.path.dirname(capnp_executable), '..', 'lib')]
|
||||
|
||||
# Try to autodetect presence of library. Requires compile/run
|
||||
# step so only works for host (non-cross) compliation
|
||||
# Look for capnproto using pkg-config (and minimum version)
|
||||
try:
|
||||
test_build(include_dirs=self.include_dirs, library_dirs=self.library_dirs)
|
||||
if pkgconfig.installed('capnp', '>= 0.8.0'):
|
||||
need_build = False
|
||||
except CompileError:
|
||||
else:
|
||||
need_build = True
|
||||
except EnvironmentError:
|
||||
# pkg-config not available in path
|
||||
need_build = True
|
||||
|
||||
if need_build:
|
||||
|
@ -157,6 +168,13 @@ class build_libcapnp_ext(build_ext_c):
|
|||
self.library_dirs += [os.path.join(build_dir, 'lib{}'.format(8 * struct.calcsize("P")))]
|
||||
self.library_dirs += [os.path.join(build_dir, 'lib')]
|
||||
|
||||
# Copy .capnp files from source
|
||||
src_glob = glob.glob(os.path.join(build_dir, 'include', 'capnp', '*.capnp'))
|
||||
dst_dir = os.path.join(self.build_lib, "capnp")
|
||||
for file in src_glob:
|
||||
info("copying {} -> {}".format(file, dst_dir))
|
||||
shutil.copy(file, dst_dir)
|
||||
|
||||
return build_ext_c.run(self)
|
||||
|
||||
extra_compile_args = ['--std=c++14']
|
||||
|
|
|
@ -1,18 +1,47 @@
|
|||
import os
|
||||
import pytest
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
examples_dir = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
||||
hostname = 'localhost'
|
||||
|
||||
|
||||
processes = []
|
||||
|
||||
@pytest.fixture
|
||||
def cleanup():
|
||||
yield
|
||||
for p in processes:
|
||||
p.kill()
|
||||
|
||||
|
||||
def run_subprocesses(address, server, client):
|
||||
cmd = [sys.executable, os.path.join(examples_dir, server), address]
|
||||
server = subprocess.Popen(cmd)
|
||||
retries = 30
|
||||
server_attempt = 0
|
||||
server_attempts = 2
|
||||
done = False
|
||||
addr, port = address.split(':')
|
||||
while not done:
|
||||
assert server_attempt < server_attempts, "Failed {} server attempts".format(server_attempts)
|
||||
server_attempt += 1
|
||||
|
||||
# Start server
|
||||
cmd = [sys.executable, os.path.join(examples_dir, server), address]
|
||||
serverp = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
|
||||
print("Server started (Attempt #{})".format(server_attempt))
|
||||
processes.append(serverp)
|
||||
retries = 300
|
||||
# Loop until we have a socket connection to the server (with timeout)
|
||||
while True:
|
||||
try:
|
||||
if 'unix' in address:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
result = sock.connect_ex(port)
|
||||
if result == 0:
|
||||
break
|
||||
else:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
result = sock.connect_ex((addr, int(port)))
|
||||
if result == 0:
|
||||
|
@ -21,55 +50,98 @@ def run_subprocesses(address, server, client):
|
|||
result = sock.connect_ex((addr, int(port)))
|
||||
if result == 0:
|
||||
break
|
||||
except socket.gaierror as err:
|
||||
print("gaierror: {}".format(err))
|
||||
# Give the server some small amount of time to start listening
|
||||
time.sleep(0.1)
|
||||
retries -= 1
|
||||
if retries == 0:
|
||||
assert False, "Timed out waiting for server to start"
|
||||
serverp.kill()
|
||||
print("Timed out waiting for server to start")
|
||||
break
|
||||
|
||||
if serverp.poll() is not None:
|
||||
print("Server exited prematurely: {}".format(serverp.returncode))
|
||||
break
|
||||
|
||||
# 3 tries per server try
|
||||
client_attempt = 0
|
||||
client_attempts = 3
|
||||
while not done:
|
||||
if client_attempt >= client_attempts:
|
||||
print("Failed {} client attempts".format(client_attempts))
|
||||
break
|
||||
client_attempt += 1
|
||||
|
||||
# Start client
|
||||
cmd = [sys.executable, os.path.join(examples_dir, client), address]
|
||||
client = subprocess.Popen(cmd)
|
||||
clientp = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
|
||||
print("Client started (Attempt #{})".format(client_attempt))
|
||||
processes.append(clientp)
|
||||
|
||||
ret = client.wait(timeout=30)
|
||||
server.kill()
|
||||
assert ret == 0
|
||||
retries = 30 * 10
|
||||
# Loop until the client is finished (with timeout)
|
||||
while True:
|
||||
if clientp.poll() == 0:
|
||||
done = True
|
||||
break
|
||||
|
||||
if clientp.poll() is not None:
|
||||
print("Client exited prematurely: {}".format(clientp.returncode))
|
||||
break
|
||||
time.sleep(0.1)
|
||||
retries -= 1
|
||||
if retries == 0:
|
||||
print("Timed out waiting for client to finish")
|
||||
clientp.kill()
|
||||
break
|
||||
|
||||
# Retrying with different address (ipv4)
|
||||
if 'unix' not in addr:
|
||||
addr = socket.gethostbyname(addr)
|
||||
address = '{}:{}'.format(addr, port)
|
||||
print("Forcing ipv4 -> {}".format(address))
|
||||
serverp.kill()
|
||||
|
||||
serverp.kill()
|
||||
|
||||
|
||||
def test_async_calculator_example():
|
||||
address = 'localhost:36432'
|
||||
def test_async_calculator_example(cleanup):
|
||||
address = '{}:36432'.format(hostname)
|
||||
server = 'async_calculator_server.py'
|
||||
client = 'async_calculator_client.py'
|
||||
run_subprocesses(address, server, client)
|
||||
|
||||
|
||||
def test_thread_example():
|
||||
address = 'localhost:36433'
|
||||
def test_thread_example(cleanup):
|
||||
address = '{}:36433'.format(hostname)
|
||||
server = 'thread_server.py'
|
||||
client = 'thread_client.py'
|
||||
run_subprocesses(address, server, client)
|
||||
|
||||
|
||||
def test_addressbook_example():
|
||||
def test_addressbook_example(cleanup):
|
||||
proc = subprocess.Popen([sys.executable, os.path.join(examples_dir, 'addressbook.py')])
|
||||
ret = proc.wait()
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_async_example():
|
||||
address = 'localhost:36434'
|
||||
def test_async_example(cleanup):
|
||||
address = '{}:36434'.format(hostname)
|
||||
server = 'async_server.py'
|
||||
client = 'async_client.py'
|
||||
run_subprocesses(address, server, client)
|
||||
|
||||
|
||||
def test_ssl_async_example():
|
||||
address = 'localhost:36435'
|
||||
def test_ssl_async_example(cleanup):
|
||||
address = '{}:36435'.format(hostname)
|
||||
server = 'async_ssl_server.py'
|
||||
client = 'async_ssl_client.py'
|
||||
run_subprocesses(address, server, client)
|
||||
|
||||
|
||||
def test_ssl_reconnecting_async_example():
|
||||
address = 'localhost:36436'
|
||||
def test_ssl_reconnecting_async_example(cleanup):
|
||||
address = '{}:36436'.format(hostname)
|
||||
server = 'async_ssl_server.py'
|
||||
client = 'async_reconnecting_ssl_client.py'
|
||||
run_subprocesses(address, server, client)
|
||||
|
|
|
@ -111,3 +111,9 @@ def test_remove_import_hook():
|
|||
|
||||
with pytest.raises(ImportError):
|
||||
import addressbook_capnp # noqa: F401
|
||||
|
||||
|
||||
def test_bundled_import_hook():
|
||||
# stream.capnp should be bundled, or provided by the system capnproto
|
||||
capnp.add_import_hook()
|
||||
import stream_capnp
|
||||
|
|
|
@ -14,6 +14,17 @@ sys.path.append(examples_dir)
|
|||
import calculator_client # noqa: E402
|
||||
import calculator_server # noqa: E402
|
||||
|
||||
# Uses run_subprocesses function
|
||||
import test_examples
|
||||
|
||||
processes = []
|
||||
|
||||
@pytest.fixture
|
||||
def cleanup():
|
||||
yield
|
||||
for p in processes:
|
||||
p.kill()
|
||||
|
||||
|
||||
def test_calculator():
|
||||
read, write = socket.socketpair()
|
||||
|
@ -22,53 +33,13 @@ def test_calculator():
|
|||
calculator_client.main(read)
|
||||
|
||||
|
||||
def run_subprocesses(address):
|
||||
cmd = [sys.executable, os.path.join(examples_dir, 'calculator_server.py'), address]
|
||||
server = subprocess.Popen(cmd)
|
||||
retries = 30
|
||||
if 'unix' in address:
|
||||
addr = address.split(':')[1]
|
||||
while True:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
result = sock.connect_ex(addr)
|
||||
if result == 0:
|
||||
break
|
||||
# Give the server some small amount of time to start listening
|
||||
time.sleep(0.1)
|
||||
retries -= 1
|
||||
if retries == 0:
|
||||
assert False, "Timed out waiting for server to start"
|
||||
else:
|
||||
addr, port = address.split(':')
|
||||
while True:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
result = sock.connect_ex((addr, int(port)))
|
||||
if result == 0:
|
||||
break
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
result = sock.connect_ex((addr, int(port)))
|
||||
if result == 0:
|
||||
break
|
||||
# Give the server some small amount of time to start listening
|
||||
time.sleep(0.1)
|
||||
retries -= 1
|
||||
if retries == 0:
|
||||
assert False, "Timed out waiting for server to start"
|
||||
cmd = [sys.executable, os.path.join(examples_dir, 'calculator_client.py'), address]
|
||||
client = subprocess.Popen(cmd)
|
||||
|
||||
ret = client.wait()
|
||||
server.kill()
|
||||
assert ret == 0
|
||||
|
||||
|
||||
def test_calculator_tcp():
|
||||
def test_calculator_tcp(cleanup):
|
||||
address = 'localhost:36431'
|
||||
run_subprocesses(address)
|
||||
test_examples.run_subprocesses(address, 'calculator_server.py', 'calculator_client.py')
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == 'nt', reason="socket.AF_UNIX not supported on Windows")
|
||||
def test_calculator_unix():
|
||||
def test_calculator_unix(cleanup):
|
||||
path = '/tmp/pycapnp-test'
|
||||
try:
|
||||
os.unlink(path)
|
||||
|
@ -76,7 +47,7 @@ def test_calculator_unix():
|
|||
pass
|
||||
|
||||
address = 'unix:' + path
|
||||
run_subprocesses(address)
|
||||
test_examples.run_subprocesses(address, 'calculator_server.py', 'calculator_client.py')
|
||||
|
||||
|
||||
def test_calculator_gc():
|
||||
|
|
Loading…
Add table
Reference in a new issue