diff --git a/.gitignore b/.gitignore index ac61a35..13b11dd 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,5 @@ tests/conf/PeerMgr /test-driver *.dmp /symbols +__pycache__/ +.cache/ diff --git a/.travis.yml b/.travis.yml index 7b1b6b2..fe483ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ sudo: false language: python compiler: - gcc - - clang addons: apt: packages: @@ -21,11 +20,10 @@ cache: directories: - $HOME/.cache/pip - $HOME/.ccache - - $HOME/downloads before_install: - ccache -s - export PATH=/usr/lib/ccache:${PATH} install: - - ./integration-tests/install-deps.sh + - ./ci/install-deps.sh script: - - ./integration-tests/run.py + - ./ci/run.py diff --git a/ci/install-deps.sh b/ci/install-deps.sh new file mode 100755 index 0000000..7953e5d --- /dev/null +++ b/ci/install-deps.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e -x + +SCRIPT=${BASH_SOURCE[0]} +TESTS_DIR=$(dirname "${SCRIPT}")/.. +SETUP_DIR=${TESTS_DIR}/ci + +cd $SETUP_DIR + +pip install -r requirements.txt + +# download precompiled libevhtp +# TODO(lins05): we should consider build from source with https://github.com/criticalstack/libevhtp in the future +libevhtp_bin=libevhtp-bin_1.2.0.tar.gz +wget https://dl.bintray.com/lins05/generic/libevhtp-bin/$libevhtp_bin +# tar xvf $libevhtp_bin --strip-components=3 -C /usr +tar xf $libevhtp_bin -C $HOME diff --git a/ci/requirements.txt b/ci/requirements.txt new file mode 100644 index 0000000..5332848 --- /dev/null +++ b/ci/requirements.txt @@ -0,0 +1,6 @@ +termcolor>=1.1.0 +requests>=2.8.0 +httpie>=0.9.9 +pytest>=3.3.2 +backports.functools_lru_cache>=1.4 +tenacity>=4.8.0 diff --git a/ci/run.py b/ci/run.py new file mode 100755 index 0000000..d978ed9 --- /dev/null +++ b/ci/run.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +""" +Install dir: ~/opt/local +Data dir: /tmp/haiwen +""" + +import argparse +import glob +import json +import logging +import os +import re +import sys +from os.path import abspath, basename, exists, expanduser, join + +import requests +import termcolor + +from serverctl import MYSQL_ROOT_PASSWD, ServerCtl +from utils import ( + cd, chdir, debug, green, info, lru_cache, mkdirs, on_travis, red, + setup_logging, shell, warning +) + +logger = logging.getLogger(__name__) + +TOPDIR = abspath(join(os.getcwd(), '..')) +if on_travis(): + PREFIX = expanduser('~/opt/local') +else: + PREFIX = os.environ.get('SEAFILE_INSTALL_PREFIX', '/usr/local') +INSTALLDIR = '/tmp/seafile-tests' + + +def num_jobs(): + return int(os.environ.get('NUM_JOBS', 2)) + + +@lru_cache() +def make_build_env(): + env = dict(os.environ) + libsearpc_dir = abspath(join(TOPDIR, 'libsearpc')) + ccnet_dir = abspath(join(TOPDIR, 'ccnet-server')) + + def _env_add(*a, **kw): + kw['env'] = env + return prepend_env_value(*a, **kw) + + _env_add('CPPFLAGS', '-I%s' % join(PREFIX, 'include'), seperator=' ') + + _env_add('LDFLAGS', '-L%s' % join(PREFIX, 'lib'), seperator=' ') + + _env_add('LDFLAGS', '-L%s' % join(PREFIX, 'lib64'), seperator=' ') + + _env_add('PATH', join(PREFIX, 'bin')) + _env_add('PYTHONPATH', join(PREFIX, 'lib/python2.7/site-packages')) + _env_add('PKG_CONFIG_PATH', join(PREFIX, 'lib', 'pkgconfig')) + _env_add('PKG_CONFIG_PATH', join(PREFIX, 'lib64', 'pkgconfig')) + _env_add('PKG_CONFIG_PATH', libsearpc_dir) + _env_add('PKG_CONFIG_PATH', ccnet_dir) + _env_add('LD_LIBRARY_PATH', join(PREFIX, 'lib')) + + for key in ('PATH', 'PKG_CONFIG_PATH', 'CPPFLAGS', 'LDFLAGS', 'PYTHONPATH'): + info('%s: %s', key, env.get(key, '')) + return env + + +def prepend_env_value(name, value, seperator=':', env=None): + '''append a new value to a list''' + env = env or os.environ + current_value = env.get(name, '') + new_value = value + if current_value: + new_value += seperator + current_value + + env[name] = new_value + return env + + +@lru_cache() +def get_branch_json_file(): + url = 'https://raw.githubusercontent.com/haiwen/seafile-test-deploy/master/branches.json' + return requests.get(url).json() + + +def get_project_branch(project, default_branch='master'): + travis_branch = os.environ.get('TRAVIS_BRANCH', 'master') + if project.name == 'seafile-server': + return travis_branch + conf = get_branch_json_file() + return conf.get(travis_branch, {}).get(project.name, default_branch) + + +class Project(object): + def __init__(self, name): + self.name = name + self.version = '' + + @property + def url(self): + return 'https://www.github.com/haiwen/{}.git'.format(self.name) + + @property + def projectdir(self): + return join(TOPDIR, self.name) + + @property + def branch(self): + return get_project_branch(self) + + def clone(self): + if exists(self.name): + with cd(self.name): + shell('git fetch origin --tags') + else: + shell( + 'git clone --depth=1 --branch {} {}'. + format(self.branch, self.url) + ) + + @chdir + def compile_and_install(self): + cmds = [ + './autogen.sh', + './configure --prefix={}'.format(PREFIX), + 'make -j{}'.format(num_jobs()), + 'make install', + ] + for cmd in cmds: + shell(cmd) + + @chdir + def use_branch(self, branch): + shell('git checkout {}'.format(branch)) + + +class Libsearpc(Project): + def __init__(self): + super(Libsearpc, self).__init__('libsearpc') + + +class CcnetServer(Project): + def __init__(self): + super(CcnetServer, self).__init__('ccnet-server') + + +class SeafileServer(Project): + def __init__(self): + super(SeafileServer, self).__init__('seafile-server') + + +def fetch_and_build(): + libsearpc = Project('libsearpc') + ccnet = CcnetServer() + seafile = SeafileServer() + + libsearpc.clone() + libsearpc.compile_and_install() + + ccnet.clone() + ccnet.compile_and_install() + + seafile.compile_and_install() + + +def parse_args(): + ap = argparse.ArgumentParser() + ap.add_argument('-v', '--verbose', action='store_true') + ap.add_argument('-t', '--test-only', action='store_true') + + return ap.parse_args() + + +def main(): + mkdirs(INSTALLDIR) + os.environ.update(make_build_env()) + args = parse_args() + if on_travis() and not args.test_only: + fetch_and_build() + # for db in ('sqlite3', 'mysql'): + for db in ('sqlite3', ): + shell('rm -rf {}/*'.format(INSTALLDIR)) + start_and_test_with_db(db) + + +def start_and_test_with_db(db): + info('Setting up seafile server with %s database', db) + server = ServerCtl(INSTALLDIR, db) + server.setup() + with server.run(): + info('Testing with %s database', db) + with cd(SeafileServer().projectdir): + shell('py.test', env=server.get_seaserv_envs()) + + +if __name__ == '__main__': + os.chdir(TOPDIR) + setup_logging() + main() diff --git a/ci/serverctl.py b/ci/serverctl.py new file mode 100755 index 0000000..f7345a5 --- /dev/null +++ b/ci/serverctl.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python +#coding: UTF-8 + +import argparse +import glob +import logging +import os +import re +import sys +from collections import namedtuple +from contextlib import contextmanager +from os.path import abspath, basename, dirname, exists, join + +import requests +from tenacity import TryAgain, retry, stop_after_attempt, wait_fixed + +from utils import ( + cd, chdir, debug, green, info, mkdirs, red, setup_logging, shell, warning +) + +logger = logging.getLogger(__name__) + +MYSQL_ROOT_PASSWD = 's123' + + +class ServerCtl(object): + def __init__(self, datadir, db='sqlite3'): + self.db = db + self.datadir = datadir + self.central_conf_dir = join(datadir, 'conf') + self.seafile_conf_dir = join(datadir, 'seafile-data') + self.ccnet_conf_dir = join(datadir, 'ccnet') + + self.log_dir = join(datadir, 'logs') + mkdirs(self.log_dir) + self.ccnet_log = join(self.log_dir, 'ccnet.log') + self.seafile_log = join(self.log_dir, 'seafile.log') + + self.ccnet_proc = None + self.seafile_proc = None + + def setup(self): + if self.db == 'mysql': + create_mysql_dbs() + + self.init_ccnet() + self.init_seafile() + + def init_ccnet(self): + cmd = [ + 'ccnet-init', + '-F', + self.central_conf_dir, + '-c', + self.ccnet_conf_dir, + '--name', + 'test', + '--host', + 'test.seafile.com', + ] + shell(cmd) + + def init_seafile(self): + cmd = [ + 'seaf-server-init', + '--central-config-dir', + self.central_conf_dir, + '--seafile-dir', + self.seafile_conf_dir, + '--fileserver-port', + '8082', + ] + + shell(cmd) + + @contextmanager + def run(self): + try: + self.start() + yield self + except: + self.print_logs() + raise + finally: + self.stop() + + def print_logs(self): + for logfile in self.ccnet_log, self.seafile_log: + if exists(logfile): + shell('cat {0}'.format(logfile)) + + @retry(wait=wait_fixed(1), stop=stop_after_attempt(10)) + def wait_ccnet_ready(self): + if not exists(join(self.ccnet_conf_dir, 'ccnet.sock')): + raise TryAgain + + def start(self): + logger.info('Starting ccnet server') + self.start_ccnet() + self.wait_ccnet_ready() + logger.info('Starting seafile server') + self.start_seafile() + + def start_ccnet(self): + cmd = [ + "ccnet-server", + "-F", + self.central_conf_dir, + "-c", + self.ccnet_conf_dir, + "-f", + self.ccnet_log, + ] + self.ccnet_proc = shell(cmd, wait=False) + + def start_seafile(self): + cmd = [ + "seaf-server", + "-F", + self.central_conf_dir, + "-c", + self.ccnet_conf_dir, + "-d", + self.seafile_conf_dir, + "-l", + self.seafile_log, + ] + self.seafile_proc = shell(cmd, wait=False) + + def stop(self): + if self.ccnet_proc: + logger.info('Stopping ccnet server') + self.ccnet_proc.terminate() + if self.seafile_proc: + logger.info('Stopping seafile server') + self.seafile_proc.terminate() + + def get_seaserv_envs(self): + envs = dict(os.environ) + envs.update({ + 'SEAFILE_CENTRAL_CONF_DIR': self.central_conf_dir, + 'CCNET_CONF_DIR': self.ccnet_conf_dir, + 'SEAFILE_CONF_DIR': self.seafile_conf_dir, + }) + return envs + + +def create_mysql_dbs(): + shell('mysqladmin -u root password %s' % MYSQL_ROOT_PASSWD) + sql = '''\ +create database `ccnet-existing` character set = 'utf8'; +create database `seafile-existing` character set = 'utf8'; +create database `seahub-existing` character set = 'utf8'; + +create user 'seafile'@'localhost' identified by 'seafile'; + +GRANT ALL PRIVILEGES ON `ccnet-existing`.* to `seafile`@localhost; +GRANT ALL PRIVILEGES ON `seafile-existing`.* to `seafile`@localhost; +GRANT ALL PRIVILEGES ON `seahub-existing`.* to `seafile`@localhost; + ''' + + shell('mysql -u root -p%s' % MYSQL_ROOT_PASSWD, inputdata=sql) diff --git a/integration-tests/utils.py b/ci/utils.py similarity index 63% rename from integration-tests/utils.py rename to ci/utils.py index fdb468b..0e908c5 100644 --- a/integration-tests/utils.py +++ b/ci/utils.py @@ -1,18 +1,23 @@ #coding: UTF-8 -import os -from os.path import abspath, basename, exists, expanduser, join -import sys -import re import logging +import os +import re +import sys from contextlib import contextmanager -from subprocess import Popen, PIPE, CalledProcessError +from os.path import abspath, basename, exists, expanduser, join +from subprocess import PIPE, CalledProcessError, Popen -import termcolor import requests -from pexpect import spawn +import termcolor + +try: + from functools import lru_cache +except ImportError: + from backports.functools_lru_cache import lru_cache + +logger = logging.getLogger(__name__) -logger = logging.getLogger(__file__) def _color(s, color): return s if not os.isatty(sys.stdout.fileno()) \ @@ -39,16 +44,19 @@ def warning(fmt, *a): logger.warn(red(fmt), *a) -def shell(cmd, inputdata=None, **kw): +def shell(cmd, inputdata=None, wait=True, **kw): info('calling "%s" in %s', cmd, kw.get('cwd', os.getcwd())) kw['shell'] = not isinstance(cmd, list) kw['stdin'] = PIPE if inputdata else None p = Popen(cmd, **kw) if inputdata: p.communicate(inputdata) - p.wait() - if p.returncode: - raise CalledProcessError(p.returncode, cmd) + if wait: + p.wait() + if p.returncode: + raise CalledProcessError(p.returncode, cmd) + else: + return p @contextmanager @@ -68,6 +76,7 @@ def chdir(func): return wrapped + def setup_logging(): kw = { 'format': '[%(asctime)s][%(module)s]: %(message)s', @@ -77,5 +86,24 @@ def setup_logging(): } logging.basicConfig(**kw) - logging.getLogger('requests.packages.urllib3.connectionpool').setLevel( - logging.WARNING) + logging.getLogger('requests.packages.urllib3.connectionpool' + ).setLevel(logging.WARNING) + + +def mkdirs(*paths): + for path in paths: + if not exists(path): + os.mkdir(path) + +def on_travis(): + return 'TRAVIS_BUILD_NUMBER' in os.environ + +@contextmanager +def cd(path): + path = expanduser(path) + olddir = os.getcwd() + os.chdir(path) + try: + yield + finally: + os.chdir(olddir) diff --git a/integration-tests/README.md b/integration-tests/README.md deleted file mode 100644 index 00790ae..0000000 --- a/integration-tests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -### Seafile Integration Tests - -The purpose of integration tests is to build a seafile release package and run tests against it. diff --git a/integration-tests/autosetup.py b/integration-tests/autosetup.py deleted file mode 100755 index af25de4..0000000 --- a/integration-tests/autosetup.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python -#coding: UTF-8 - -import os -from os.path import abspath, basename, exists, dirname, join -import sys -import argparse -import re -from collections import namedtuple - -import requests -from pexpect import spawn - -from utils import green, red, debug, info, warning, cd, shell, chdir, setup_logging - -USERNAME = 'test@seafiletest.com' -PASSWORD = 'testtest' -ADMIN_USERNAME = 'admin@seafiletest.com' -ADMIN_PASSWORD = 'adminadmin' -MYSQL_ROOT_PASSWD = 's123' - -ServerConfig = namedtuple('ServerConfig', [ - 'installdir', - 'tarball', - 'version', - 'initmode', -]) - - -def setup_server(cfg, db): - '''Setup seafile server with the setup-seafile.sh script. We use pexpect to - interactive with the setup process of the script. - ''' - info('uncompressing server tarball') - shell('tar xf seafile-server_{}_x86-64.tar.gz -C {}' - .format(cfg.version, cfg.installdir)) - if db == 'mysql': - autosetup_mysql(cfg) - else: - autosetup_sqlite3(cfg) - - with open(join(cfg.installdir, 'conf/seahub_settings.py'), 'a') as fp: - fp.write('\n') - fp.write('DEBUG = True') - fp.write('\n') - fp.write('''\ -REST_FRAMEWORK = { - 'DEFAULT_THROTTLE_RATES': { - 'ping': '600/minute', - 'anon': '1000/minute', - 'user': '1000/minute', - }, -}''') - fp.write('\n') - - -def autosetup_sqlite3(cfg): - setup_script = get_script(cfg, 'setup-seafile.sh') - shell('''sed -i -e '/^check_root;.*/d' "{}"'''.format(setup_script)) - - if cfg.initmode == 'prompt': - setup_sqlite3_prompt(setup_script) - else: - setup_sqlite3_auto(setup_script) - -def setup_sqlite3_prompt(setup_script): - info('setting up seafile server with pexepct, script %s', setup_script) - answers = [ - ('ENTER', ''), - # server name - ('server name', 'my-seafile'), - # ip or domain - ('ip or domain', '127.0.0.1'), - # seafile data dir - ('seafile-data', ''), - # fileserver port - ('seafile fileserver', ''), - ('ENTER', ''), - ('ENTER', ''), - ] - _answer_questions(setup_script, answers) - -def setup_sqlite3_auto(setup_script): - info('setting up seafile server in auto mode, script %s', setup_script) - env = os.environ.copy() - env['SERVER_IP'] = '127.0.0.1' - shell('%s auto -n my-seafile' % setup_script, env=env) - -def createdbs(): - sql = '''\ -create database `ccnet-existing` character set = 'utf8'; -create database `seafile-existing` character set = 'utf8'; -create database `seahub-existing` character set = 'utf8'; - -create user 'seafile'@'localhost' identified by 'seafile'; - -GRANT ALL PRIVILEGES ON `ccnet-existing`.* to `seafile`@localhost; -GRANT ALL PRIVILEGES ON `seafile-existing`.* to `seafile`@localhost; -GRANT ALL PRIVILEGES ON `seahub-existing`.* to `seafile`@localhost; - ''' - - shell('mysql -u root -p%s' % MYSQL_ROOT_PASSWD, inputdata=sql) - - -def autosetup_mysql(cfg): - setup_script = get_script(cfg, 'setup-seafile-mysql.sh') - if not exists(setup_script): - print 'please specify seafile script path' - - if cfg.initmode == 'prompt': - createdbs() - setup_mysql_prompt(setup_script) - else : - # in auto mode, test create new db - setup_mysql_auto(setup_script) - -def setup_mysql_prompt(setup_script): - info('setting up seafile server with pexepct, script %s', setup_script) - answers = [ - ('ENTER', ''), - # server name - ('server name', 'my-seafile'), - # ip or domain - ('ip or domain', '127.0.0.1'), - # seafile data dir - ('seafile-data', ''), - # fileserver port - ('seafile fileserver', ''), - # use existing - ('choose a way to initialize seafile databases', '2'), - ('host of mysql server', ''), - ('port of mysql server', ''), - ('Which mysql user', 'seafile'), - ('password for mysql user', 'seafile'), - ('ccnet database', 'ccnet-existing'), - ('seafile database', 'seafile-existing'), - ('seahub database', 'seahub-existing'), - ('ENTER', ''), - ] - _answer_questions(abspath(setup_script), answers) - -def setup_mysql_auto(setup_script): - info('setting up seafile server in auto mode, script %s', setup_script) - env = os.environ.copy() - env['MYSQL_USER'] = 'seafile-new' - env['MYSQL_USER_PASSWD'] = 'seafile' - env['MYSQL_ROOT_PASSWD']= MYSQL_ROOT_PASSWD - env['CCNET_DB'] = 'ccnet-new' - env['SEAFILE_DB'] = 'seafile-new' - env['SEAHUB_DB'] = 'seahub-new' - shell('%s auto -n my-seafile -e 0' % setup_script, env=env) - -def start_server(cfg): - with cd(cfg.installdir): - shell('find . -maxdepth 2 | sort | xargs ls -lhd') - seafile_sh = get_script(cfg, 'seafile.sh') - shell('{} start'.format(seafile_sh)) - - info('starting seahub') - seahub_sh = get_script(cfg, 'seahub.sh') - answers = [ - # admin email/pass - ('admin email', ADMIN_USERNAME), - ('admin password', ADMIN_PASSWORD), - ('admin password again', ADMIN_PASSWORD), - ] - _answer_questions('{} start'.format(abspath(seahub_sh)), answers) - with cd(cfg.installdir): - shell('find . -maxdepth 2 | sort | xargs ls -lhd') - # shell('sqlite3 ccnet/PeerMgr/usermgr.db "select * from EmailUser"', cwd=INSTALLDIR) - shell('http -v localhost:8000/api2/server-info/ || true') - # shell('http -v -f POST localhost:8000/api2/auth-token/ username=admin@seafiletest.com password=adminadmin || true') - shell('netstat -nltp') - - -def _answer_questions(cmd, answers): - info('expect: spawing %s', cmd) - child = spawn(cmd) - child.logfile = sys.stdout - - def autofill(pattern, line): - child.expect(pattern) - child.sendline(line) - - for k, v in answers: - autofill(k, v) - child.sendline('') - child.logfile = None - child.interact() - - -def get_script(cfg, path): - """ - :type cfg: ServerConfig - """ - return join(server_dir(cfg), path) - - -def server_dir(cfg): - """ - :type cfg: ServerConfig - """ - return join(cfg.installdir, 'seafile-server-{}'.format(cfg.version)) - - -def apiurl(path): - path = path.lstrip('/') - root = os.environ.get('SEAFILE_SERVER', 'http://127.0.0.1:8000') - return '{}/api2/{}'.format(root, path) - - -def create_test_user(cfg): - data = {'username': ADMIN_USERNAME, 'password': ADMIN_PASSWORD, } - res = requests.post(apiurl('/auth-token/'), data=data) - debug('%s %s', res.status_code, res.text) - token = res.json()['token'] - data = {'password': PASSWORD, } - headers = {'Authorization': 'Token ' + token} - res = requests.put( - apiurl('/accounts/{}/'.format(USERNAME)), - data=data, - headers=headers) - assert res.status_code == 201 - - -def main(): - ap = argparse.ArgumentParser() - ap.add_argument('-v', '--verbose', action='store_true') - ap.add_argument('--db', choices=('sqlite3', 'mysql'), default='sqlite3') - ap.add_argument('installdir') - ap.add_argument('tarball') - args = ap.parse_args() - - if not exists(args.installdir): - print 'directory {} does not exist'.format(args.installdir) - sys.exit(1) - - if os.listdir(args.installdir): - print 'directory {} is not empty'.format(args.installdir) - sys.exit(1) - - if not exists(args.tarball): - print 'file {} does not exist'.format(args.tarball) - sys.exit(1) - - m = re.match(r'^.*?_([\d\.]+).*?\.tar\.gz$', basename(args.tarball)) - version = m.group(1) - - cfg = ServerConfig(installdir=args.installdir, - tarball=args.tarball, - version=version) - setup_server(cfg, args.db) - start_server(cfg) - create_test_user(cfg) - - -if __name__ == '__main__': - setup_logging() - main() diff --git a/integration-tests/install-deps.sh b/integration-tests/install-deps.sh deleted file mode 100755 index 412a067..0000000 --- a/integration-tests/install-deps.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -set -e -x - -pip install -r ./integration-tests/requirements.txt - -pushd $HOME - -# download precompiled libevhtp -libevhtp_bin=libevhtp-bin_1.2.0.tar.gz -wget https://dl.bintray.com/lins05/generic/libevhtp-bin/$libevhtp_bin -tar xf $libevhtp_bin -find $HOME/opt - -# download seahub thirdpart python libs -WGET="wget --no-check-certificate" -downloads=$HOME/downloads -thirdpart=$HOME/thirdpart - -mkdir -p $downloads $thirdpart -cd $thirdpart -save_pythonpath=$PYTHONPATH -export PYTHONPATH=. -urls=( - https://pypi.python.org/packages/source/p/pytz/pytz-2016.1.tar.gz - https://www.djangoproject.com/m/releases/1.8/Django-1.8.10.tar.gz - https://pypi.python.org/packages/source/d/django-statici18n/django-statici18n-1.1.3.tar.gz - https://pypi.python.org/packages/source/d/djangorestframework/djangorestframework-3.3.2.tar.gz - https://pypi.python.org/packages/source/d/django_compressor/django_compressor-1.4.tar.gz - - https://pypi.python.org/packages/source/j/jsonfield/jsonfield-1.0.3.tar.gz - https://pypi.python.org/packages/source/d/django-post_office/django-post_office-2.0.6.tar.gz - - http://pypi.python.org/packages/source/g/gunicorn/gunicorn-19.4.5.tar.gz - http://pypi.python.org/packages/source/f/flup/flup-1.0.2.tar.gz - https://pypi.python.org/packages/source/c/chardet/chardet-2.3.0.tar.gz - https://labix.org/download/python-dateutil/python-dateutil-1.5.tar.gz - https://pypi.python.org/packages/source/s/six/six-1.9.0.tar.gz - - https://pypi.python.org/packages/source/d/django-picklefield/django-picklefield-0.3.2.tar.gz - https://pypi.python.org/packages/source/d/django-constance/django-constance-1.0.1.tar.gz - - https://pypi.python.org/packages/source/j/jdcal/jdcal-1.2.tar.gz - https://pypi.python.org/packages/source/e/et_xmlfile/et_xmlfile-1.0.1.tar.gz - https://pypi.python.org/packages/source/o/openpyxl/openpyxl-2.3.0.tar.gz -) -for url in ${urls[*]}; do - path="${downloads}/$(basename $url)" - if [[ ! -e $path ]]; then - $WGET -O $path $url - fi - easy_install -d . $path -done -export PYTHONPATH=$save_pythonpath - -popd diff --git a/integration-tests/requirements.txt b/integration-tests/requirements.txt deleted file mode 100644 index bfebfe9..0000000 --- a/integration-tests/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -Pillow==4.1.0 -termcolor==1.1.0 -prettytable==0.7.2 -pexpect==4.0 -requests==2.8.0 -httpie -django-constance[database] -MySQL-python==1.2.5 diff --git a/integration-tests/run.py b/integration-tests/run.py deleted file mode 100755 index f537caf..0000000 --- a/integration-tests/run.py +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env python - -import os -from os.path import abspath, basename, exists, expanduser, join -import sys -import re -import glob -import json -import logging -import requests - -import termcolor -from pexpect import spawn -from utils import green, red, debug, info, warning, cd, shell, chdir, setup_logging -from autosetup import (setup_server, ServerConfig, get_script, server_dir, - start_server, create_test_user, MYSQL_ROOT_PASSWD) - -TOPDIR = abspath(join(os.getcwd(), '..')) -PREFIX = expanduser('~/opt/local') -SRCDIR = '/tmp/src' -INSTALLDIR = '/tmp/haiwen' -THIRDPARTDIR = expanduser('~/thirdpart') - -logger = logging.getLogger(__file__) -seafile_version = '' - -TRAVIS_BRANCH = os.environ.get('TRAVIS_BRANCH', 'master') - - -def make_build_env(): - env = dict(os.environ) - libsearpc_dir = abspath(join(TOPDIR, 'libsearpc')) - ccnet_dir = abspath(join(TOPDIR, 'ccnet-server')) - - def _env_add(*a, **kw): - kw['env'] = env - return prepend_env_value(*a, **kw) - - _env_add('CPPFLAGS', '-I%s' % join(PREFIX, 'include'), seperator=' ') - - _env_add('LDFLAGS', '-L%s' % os.path.join(PREFIX, 'lib'), seperator=' ') - - _env_add('LDFLAGS', '-L%s' % os.path.join(PREFIX, 'lib64'), seperator=' ') - - _env_add('PATH', os.path.join(PREFIX, 'bin')) - _env_add('PATH', THIRDPARTDIR) - _env_add('PKG_CONFIG_PATH', os.path.join(PREFIX, 'lib', 'pkgconfig')) - _env_add('PKG_CONFIG_PATH', os.path.join(PREFIX, 'lib64', 'pkgconfig')) - _env_add('PKG_CONFIG_PATH', libsearpc_dir) - _env_add('PKG_CONFIG_PATH', ccnet_dir) - - for key in ('PATH', 'PKG_CONFIG_PATH', 'CPPFLAGS', 'LDFLAGS', - 'PYTHONPATH'): - info('%s: %s', key, env.get(key, '')) - return env - - -def prepend_env_value(name, value, seperator=':', env=None): - '''append a new value to a list''' - env = env or os.environ - current_value = env.get(name, '') - new_value = value - if current_value: - new_value += seperator + current_value - - env[name] = new_value - return env - - -def get_project_branch(project, default_branch='master'): - if project.name == 'seafile-server': - return TRAVIS_BRANCH - conf = json.loads(requests.get( - 'https://raw.githubusercontent.com/haiwen/seafile-test-deploy/master/branches.json').text) - return conf.get(TRAVIS_BRANCH, {}).get(project.name, - default_branch) - - -class Project(object): - configure_cmd = './configure' - - def __init__(self, name): - self.name = name - self.version = '' - - @property - def url(self): - return 'https://www.github.com/haiwen/{}.git'.format(self.name) - - @property - def projectdir(self): - return join(TOPDIR, self.name) - - @property - def branch(self): - return get_project_branch(self) - - def clone(self): - if exists(self.name): - with cd(self.name): - shell('git fetch origin --tags') - else: - shell('git clone --depth=1 --branch {} {}'.format(self.branch, - self.url)) - - @chdir - def make_dist(self): - info('making tarball for %s', self.name) - if exists('./autogen.sh'): - shell('./autogen.sh') - shell(self.configure_cmd, env=make_build_env()) - shell('make dist') - - @chdir - def copy_dist(self): - self.make_dist() - tarball = glob.glob('*.tar.gz')[0] - info('copying %s to %s', tarball, SRCDIR) - shell('cp {} {}'.format(tarball, SRCDIR)) - if self.name == 'seafile-server': - name = 'seafile' - elif self.name == 'ccnet-server': - name = 'ccnet' - else: - name = self.name - m = re.match('{}-(.*).tar.gz'.format(name), basename(tarball)) - if m: - self.version = m.group(1) - - @chdir - def use_branch(self, branch): - shell('git checkout {}'.format(branch)) - - -class CcnetServer(Project): - def __init__(self): - super(CcnetServer, self).__init__('ccnet-server') - - -class SeafileServer(Project): - configure_cmd = './configure' - - def __init__(self): - super(SeafileServer, self).__init__('seafile-server') - - @chdir - def copy_dist(self): - super(SeafileServer, self).copy_dist() - global seafile_version - seafile_version = self.version - - -class Seahub(Project): - def __init__(self): - super(Seahub, self).__init__('seahub') - - @chdir - def make_dist(self): - cmds = [ - # 'git add -f media/css/*.css', - # 'git commit -a -m "%s"' % msg, - './tools/gen-tarball.py --version={} --branch=HEAD >/dev/null' - .format(seafile_version), - ] - for cmd in cmds: - shell(cmd, env=make_build_env()) - - -class SeafDAV(Project): - def __init__(self): - super(SeafDAV, self).__init__('seafdav') - - @chdir - def make_dist(self): - shell('make') - - -class SeafObj(Project): - def __init__(self): - super(SeafObj, self).__init__('seafobj') - - @chdir - def make_dist(self): - shell('make dist') - - -def build_server(libsearpc, ccnet, seafile): - cmd = [ - 'python', - join(TOPDIR, 'seafile-server/scripts/build/build-server.py'), - '--yes', - '--version=%s' % seafile.version, - '--libsearpc_version=%s' % libsearpc.version, - '--ccnet_version=%s' % ccnet.version, - '--seafile_version=%s' % seafile.version, - '--thirdpartdir=%s' % THIRDPARTDIR, - '--srcdir=%s' % SRCDIR, - '--jobs=4', - ] - shell(cmd, shell=False, env=make_build_env()) - - -def fetch_and_build(): - libsearpc = Project('libsearpc') - ccnet = CcnetServer() - seafile = SeafileServer() - seahub = Seahub() - seafobj = SeafObj() - seafdav = SeafDAV() - - for project in (libsearpc, ccnet, seafile, seahub, seafdav, seafobj): - if project.name != 'seafile-server': - project.clone() - project.copy_dist() - - build_server(libsearpc, ccnet, seafile) - - -def run_tests(cfg): - # run_python_seafile_tests() - # run_seafdav_tests(cfg) - # must stop seafile server before running seaf-gc - shell('{} stop'.format(get_script(cfg, 'seafile.sh'))) - shell('{} stop'.format(get_script(cfg, 'seahub.sh'))) - shell('{} --verbose --rm-deleted'.format(get_script(cfg, 'seaf-gc.sh'))) - - -def run_python_seafile_tests(): - python_seafile = Project('python-seafile') - if not exists(python_seafile.projectdir): - python_seafile.clone() - shell('pip install -r {}/requirements.txt'.format( - python_seafile.projectdir)) - - with cd(python_seafile.projectdir): - # install python-seafile because seafdav tests needs it - shell('python setup.py install') - shell('py.test') - - -def _seafdav_env(cfg): - env = dict(os.environ) - env['CCNET_CONF_DIR'] = join(INSTALLDIR, 'ccnet') - env['SEAFILE_CONF_DIR'] = join(INSTALLDIR, 'seafile-data') - env['SEAFILE_CENTRAL_CONF_DIR'] = join(INSTALLDIR, 'conf') - for path in glob.glob(join( - server_dir(cfg), 'seafile/lib*/python*/*-packages')): - prepend_env_value('PYTHONPATH', path, env=env) - return env - - -def run_seafdav_tests(cfg): - seafdav = SeafDAV() - shell('pip install -r {}/test-requirements.txt'.format(seafdav.projectdir)) - with cd(seafdav.projectdir): - shell('nosetests -v -s', env=_seafdav_env(cfg)) - - -def _mkdirs(*paths): - for path in paths: - if not exists(path): - os.mkdir(path) - - -def main(): - _mkdirs(SRCDIR, INSTALLDIR) - setup_logging() - fetch_and_build() - for db in ('sqlite3', 'mysql'): - if db == 'mysql': - shell('mysqladmin -u root password %s' % MYSQL_ROOT_PASSWD) - for i in ('prompt', 'auto'): - shell('rm -rf {}/*'.format(INSTALLDIR)) - setup_and_test(db, i) - - -def setup_and_test(db, initmode): - cfg = ServerConfig( - installdir=INSTALLDIR, - tarball=join(TOPDIR, 'seafile-server_{}_x86-64.tar.gz'.format( - seafile_version)), - version=seafile_version, - initmode=initmode) - info('Setting up seafile server with %s database', db) - setup_server(cfg, db) - # enable webdav, we're going to seafdav tests later - shell('''sed -i -e "s/enabled = false/enabled = true/g" {}''' - .format(join(INSTALLDIR, 'conf/seafdav.conf'))) - try: - start_server(cfg) - info('Testing seafile server with %s database', db) - create_test_user(cfg) - run_tests(cfg) - except: - for logfile in glob.glob('{}/logs/*.log'.format(INSTALLDIR)): - shell('echo {0}; cat {0}'.format(logfile)) - for logfile in glob.glob('{}/seafile-server-{}/runtime/*.log'.format( - INSTALLDIR, seafile_version)): - shell('echo {0}; cat {0}'.format(logfile)) - raise - - -if __name__ == '__main__': - os.chdir(TOPDIR) - # Add the location where libevhtp is installed so ldd can know it. - prepend_env_value('LD_LIBRARY_PATH', os.path.expanduser('~/opt/local/lib')) - main() diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..3122f91 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +addopts = -vv -s +log_format = %(asctime)s:%(name)s:%(levelname)s:%(message)s +log_date_format = %Y-%m-%d %H:%M:%S +# log_cli_level = info \ No newline at end of file diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..226cbbf --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +SCRIPT=${BASH_SOURCE[0]} +PROJECT_DIR=$(dirname "${SCRIPT}") + +cd $PROJECT_DIR + +export PYTHONPATH=$PROJECT_DIR:$PYTHONPATH + +ci/run.py --test-only diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conf/ccnet.conf b/tests/conf/ccnet.conf deleted file mode 100644 index 70186f9..0000000 --- a/tests/conf/ccnet.conf +++ /dev/null @@ -1,11 +0,0 @@ -[General] -USER_NAME = server -ID = 8e4b13b49ca79f35732d9f44a0804940d985627c -NAME = server -SERVICE_URL = http://127.0.0.1 - -[Network] -PORT = 10002 - -[Client] -PORT = 9999 diff --git a/tests/conf/mykey.peer b/tests/conf/mykey.peer deleted file mode 100644 index d9dd50c..0000000 --- a/tests/conf/mykey.peer +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAuZFwgxkKQGaqYyFMxIUz1JHnZPaOgEQ+fX/jRVYbGMiHkSbX -K9X3XUHUGEjUt8b3zW6UZJGjgyV5S08YuaN0eE5z6Q6bnuWEhkTmgZgXaybc9Hiu -y2WAHpKj+qbXcmewE0WEys/Ov9AIe0TRXmvL6r1793VcLSzgb/aIQA2WFg97DfEA -hGAHo5BesKRfEEvXL6ZB9cGxXP9qIy0ObTvLXlOgbYchfV4rrXJk0u9xWjRyXABv -2Myv3fgxmGmTR+TAw2G5GCKeh9IoIuWVMGPyjSlERGMqQYymNz3NgyWFayyZ5HQS -tihCnflOGEiMHRkOwIczB16YZhan2YqKpsjHGwIBIwKCAQEArvbXzBBLfoyvR4XM -Cb9rYgXozOh3usQAZ7MYHM2HQ0C6VahHN/WgFhl+1RF4Gv1tTKoW4nqwHJEL9oxn -xPkzTNxBZrYAcT7NaKdc/diLG+LQVDdFuHWkrxyL+vUUR0vR5kjcSjGlrYmhmMvb -WQaNEIbFVwhA92TTnMPfjNmcI2wRKI1K9NEKDAMIPSwW/sgkls2h4KW3Y7DooJ0k -l0apjN/rlaR4ohZp6oMVifW8GFY43Xau+4dIrYTnvvSyvGvtB+8cWuhqqvWHRZdM -rFjgOJoZH5l0zxt2dYW2WFiqgT7xXsvu6L+nylXktEMxC33rehYdPrd427J409A6 -caO5cwKBgQDyrBQ8UXu7cDAktiKTwH7+pA0wNyTvKsGYw0RcFILccpxty2r5gYhI -eLFPVyjoYxwauW6vX3cSAYLKR+2PlYvkPpEvBQIJbaurx++ejez/KxYD65ZeFTfs -Kb9A08hgMxCvJmnRvojhez1OZmmmWYPT57XeZXnCiNoyJWKA0mMNvwKBgQDDwn02 -o5n7ugetXIlV1PiStVogPPTBobh9jsXooQFh4fB+lsrO082hapMlbVVNG1gLzvTY -V0oDM/AzdnC6feZlAEdM+IcruinVnMnbnhiwPVDInCJIhvmJ/XScvkTsgHwRiAss -Tlf8wH/uGXiaeVV/KMlkKRK6h54znTPq37/VpQKBgQDkziG1NuJgRTS05j3bxB/3 -Z3omJV1Wh2YTsMtswuHIiVGpWWTcnrOyC2VZb2+2iVUDQR83oycfmwZJsYg27BYu -+SnNPzxvSiWEtTJiS00rGf7QfwoeMUNbAspEb+jPux5b/6WZ34hfkXRRO/02cagu -Mj3DDzhJtDtxG+8pAOEM9QKBgQC+KqWFiPv72UlJUpQKPJmzFpIQsD44cTbgXs7h -+32viwbhX0irqS4nxp2SEnAfBJ6sYqS05xSyp3uftOKJRxpTfJ0I8W1drYe5kP6a -1Bf7qUcpRzc/JAhaKWn3Wb9MJQrPM7MVGOfCVJmINgAhCCcrEa2xwX/oZnxsp1cB -a6RpIwKBgQDW15IebNwVOExTqtfh6UvIjMSrk9OoHDyjoPLI3eyPt3ujKdXFJ8qF -CWg9ianQyE5Y8vfDI+x1YRCOwq2WapeXzkSO8CzVFHgz5kFqJQolr4+o6wr5mLLC -+6iW9u81/X3bMAWshtNfsWbRSFLT1WNVTKRg+xO7YG/3wcyeIeqigA== ------END RSA PRIVATE KEY----- diff --git a/tests/config.py b/tests/config.py new file mode 100644 index 0000000..c70841c --- /dev/null +++ b/tests/config.py @@ -0,0 +1,9 @@ +USER = 'testuser@test.seafile.com' +PASSWORD = 'testuser' +USER2 = 'testuser2@test.seafile.com' +PASSWORD2 = 'testuser2' +ADMIN_USER = 'adminuser@test.seafile.com' +ADMIN_PASSWORD = 'adminuser' + +INACTIVE_USER = 'inactiveuser@test.seafile.com' +INACTIVE_PASSWORD = 'inactiveuser' diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..f0ba04d --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,51 @@ +#coding: UTF-8 + +import logging +import os + +import pytest +from tenacity import retry, stop_after_attempt, wait_fixed +from tests.config import ( + ADMIN_PASSWORD, ADMIN_USER, INACTIVE_PASSWORD, INACTIVE_USER, PASSWORD, + PASSWORD2, USER, USER2 +) +from tests.utils import create_and_get_repo, randstring + +from seaserv import ccnet_api, seafile_api + +logger = logging.getLogger(__name__) + + +@retry(wait=wait_fixed(2), stop=stop_after_attempt(10)) +def wait_for_server(): + seafile_api.get_repo_list(0, 1) + + +@pytest.fixture(scope='session', autouse=True) +def create_users(): + """ + Create an admin user and a normal user + """ + wait_for_server() + logger.info('preparing users for testing') + ccnet_api.add_emailuser(USER, PASSWORD, is_staff=False, is_active=True) + ccnet_api.add_emailuser(USER2, PASSWORD2, is_staff=False, is_active=True) + ccnet_api.add_emailuser( + INACTIVE_USER, INACTIVE_PASSWORD, is_staff=False, is_active=False + ) + ccnet_api.add_emailuser( + ADMIN_USER, ADMIN_PASSWORD, is_staff=True, is_active=True + ) + + +@pytest.yield_fixture(scope='function') +def repo(): + repo = create_and_get_repo( + 'testrepo测试-{}'.format(randstring(10)), '', USER, passwd=None + ) + try: + yield repo + finally: + if seafile_api.get_repo(repo.id): + # The repo may be deleted in the test case + seafile_api.remove_repo(repo.id) diff --git a/tests/test_sharing.py b/tests/test_sharing.py new file mode 100644 index 0000000..cf1f122 --- /dev/null +++ b/tests/test_sharing.py @@ -0,0 +1,19 @@ +import pytest +from seaserv import seafile_api as api +from seaserv import ccnet_api + +from tests.config import ADMIN_USER, USER, USER2 + + +@pytest.mark.parametrize('permission', ['r', 'rw']) +def test_share_repo(repo, permission): + assert api.check_permission(repo.id, USER2) is None + + api.share_repo(repo.id, USER, USER2, permission) + assert api.check_permission(repo.id, USER2) == permission + + repos = api.get_share_in_repo_list(USER2, 0, 1) + assert len(repos) == 1 + r = repos[0] + assert r.id == repo.id + assert r.permission == permission diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..5ef86ca --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,15 @@ +import os +import random +import string + +from seaserv import ccnet_api, seafile_api + + +def create_and_get_repo(*a, **kw): + repo_id = seafile_api.create_repo(*a, **kw) + repo = seafile_api.get_repo(repo_id) + return repo + + +def randstring(length=12): + return ''.join(random.choice(string.lowercase) for i in range(length))