diff --git a/srcpkgs/backblaze-b2/files/test_b2_command_line.py b/srcpkgs/backblaze-b2/files/test_b2_command_line.py new file mode 100644 index 00000000000..a62c1c1ff84 --- /dev/null +++ b/srcpkgs/backblaze-b2/files/test_b2_command_line.py @@ -0,0 +1,861 @@ +#!/usr/bin/env python2 +###################################################################### +# +# File: test_b2_command_line.py +# +# Copyright 2019 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### + +from __future__ import print_function +import hashlib +import json +import os.path +import platform +import random +import re +import shutil +import six +import subprocess +import sys +import tempfile +import threading +import unittest + +from b2sdk.utils import fix_windows_path_limit + +USAGE = """ +This program tests the B2 command-line client. + +Usages: + + {command} [basic | sync_down | sync_up | sync_up_no_prefix + keys | sync_long_path | download | account] + + The optional last argument specifies which of the tests to run. If not + specified, all test will run. Runs the b2 package in the current directory. + + {command} test + + Runs internal unit tests. +""" + + +def usage_and_exit(): + print(USAGE.format(command=sys.argv[0]), file=sys.stderr) + sys.exit(1) + + +def error_and_exit(message): + print('ERROR:', message) + sys.exit(1) + + +def read_file(path): + with open(path, 'rb') as f: + return f.read() + + +def write_file(path, contents): + with open(path, 'wb') as f: + f.write(contents) + + +def file_mod_time_millis(path): + return int(os.path.getmtime(path) * 1000) + + +def set_file_mod_time_millis(path, time): + os.utime(path, (os.path.getatime(path), time / 1000)) + + +def random_hex(length): + return ''.join(random.choice('0123456789abcdef') for i in six.moves.xrange(length)) + + +class TempDir(object): + def __init__(self): + self.dirpath = None + + def get_dir(self): + return self.dirpath + + def __enter__(self): + self.dirpath = tempfile.mkdtemp() + return self.dirpath + + def __exit__(self, exc_type, exc_val, exc_tb): + shutil.rmtree(fix_windows_path_limit(self.dirpath)) + + +class StringReader(object): + def __init__(self): + self.string = None + + def get_string(self): + return self.string + + def read_from(self, f): + try: + self.string = f.read() + except Exception as e: + print(e) + self.string = str(e) + + +def remove_insecure_platform_warnings(text): + return os.linesep.join( + line for line in text.split(os.linesep) + if ('SNIMissingWarning' not in line) and ('InsecurePlatformWarning' not in line) + ) + + +def run_command(path_to_script, args): + """ + :param command: A list of strings like ['ls', '-l', '/dev'] + :return: (status, stdout, stderr) + """ + + # We'll run the b2 command-line by running the b2 module from + # the current directory. Python 2.6 doesn't support using + # '-m' with a package, so we explicitly say to run the module + # b2.__main__ + os.environ['PYTHONPATH'] = '.' + os.environ['PYTHONIOENCODING'] = 'utf-8' + command = ['python', '-m', 'b2.__main__'] + command.extend(args) + + print('Running:', ' '.join(command)) + + stdout = StringReader() + stderr = StringReader() + p = subprocess.Popen( + command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=platform.system() != 'Windows' + ) + p.stdin.close() + reader1 = threading.Thread(target=stdout.read_from, args=[p.stdout]) + reader1.start() + reader2 = threading.Thread(target=stderr.read_from, args=[p.stderr]) + reader2.start() + p.wait() + reader1.join() + reader2.join() + + stdout_decoded = remove_insecure_platform_warnings(stdout.get_string().decode('utf-8')) + stderr_decoded = remove_insecure_platform_warnings(stderr.get_string().decode('utf-8')) + + print_output(p.returncode, stdout_decoded, stderr_decoded) + return p.returncode, stdout_decoded, stderr_decoded + + +def print_text_indented(text): + """ + Prints text that may include weird characters, indented four spaces. + """ + for line in text.split(os.linesep): + print(' ', repr(line)[1:-1]) + + +def print_json_indented(value): + """ + Converts the value to JSON, then prints it. + """ + print_text_indented(json.dumps(value, indent=4, sort_keys=True)) + + +def print_output(status, stdout, stderr): + print(' status:', status) + if stdout != '': + print(' stdout:') + print_text_indented(stdout) + if stderr != '': + print(' stderr:') + print_text_indented(stderr) + print() + + +class CommandLine(object): + + PROGRESS_BAR_PATTERN = re.compile(r'.*B/s]$', re.DOTALL) + + EXPECTED_STDERR_PATTERNS = [ + PROGRESS_BAR_PATTERN, + re.compile(r'^$') # empty line + ] + + def __init__(self, path_to_script): + self.path_to_script = path_to_script + + def run_command(self, args): + """ + Runs the command with the given arguments, returns a tuple in form of + (succeeded, stdout) + """ + status, stdout, stderr = run_command(self.path_to_script, args) + return status == 0 and stderr == '', stdout + + def should_succeed(self, args, expected_pattern=None): + """ + Runs the command-line with the given arguments. Raises an exception + if there was an error; otherwise, returns the stdout of the command + as as string. + """ + status, stdout, stderr = run_command(self.path_to_script, args) + if status != 0: + print('FAILED with status', status) + sys.exit(1) + if stderr != '': + failed = False + for line in (s.strip() for s in stderr.split(os.linesep)): + if not any(p.match(line) for p in self.EXPECTED_STDERR_PATTERNS): + print('Unexpected stderr line:', repr(line)) + failed = True + if failed: + print('FAILED because of stderr') + print(stderr) + sys.exit(1) + if expected_pattern is not None: + if re.search(expected_pattern, stdout) is None: + print('STDOUT:') + print(stdout) + error_and_exit('did not match pattern: ' + expected_pattern) + return stdout + + def should_succeed_json(self, args): + """ + Runs the command-line with the given arguments. Raises an exception + if there was an error; otherwise, treats the stdout as JSON and returns + the data in it. + """ + return json.loads(self.should_succeed(args)) + + def should_fail(self, args, expected_pattern): + """ + Runs the command-line with the given args, expecting the given pattern + to appear in stderr. + """ + status, stdout, stderr = run_command(self.path_to_script, args) + if status == 0: + print('ERROR: should have failed') + sys.exit(1) + if re.search(expected_pattern, stdout + stderr) is None: + print(expected_pattern) + print(stdout + stderr) + error_and_exit('did not match pattern: ' + expected_pattern) + + def list_file_versions(self, bucket_name): + return self.should_succeed_json(['list_file_versions', bucket_name])['files'] + + +class TestCommandLine(unittest.TestCase): + def test_stderr_patterns(self): + progress_bar_line = './b2: 0%| | 0.00/33.3K [00:00= 4: + tests_to_run = sys.argv[3:] + for test_name in tests_to_run: + if test_name not in test_map: + error_and_exit('unknown test: "%s"' % (test_name,)) + else: + tests_to_run = sorted(six.iterkeys(test_map)) + + if os.environ.get('B2_ACCOUNT_INFO') is not None: + del os.environ['B2_ACCOUNT_INFO'] + + b2_tool = CommandLine(path_to_script) + + global_dirty = False + # Run each of the tests in its own empty bucket + for test_name in tests_to_run: + + print('#') + print('# Cleaning and making bucket for:', test_name) + print('#') + print() + + b2_tool.should_succeed(['clear_account']) + + b2_tool.should_succeed(['authorize_account', account_id, application_key]) + + bucket_name_prefix = 'test-b2-command-line-' + account_id + if not defer_cleanup: + clean_buckets(b2_tool, bucket_name_prefix) + bucket_name = bucket_name_prefix + '-' + random_hex(8) + + success, _ = b2_tool.run_command(['create_bucket', bucket_name, 'allPublic']) + if not success: + clean_buckets(b2_tool, bucket_name_prefix) + b2_tool.should_succeed(['create_bucket', bucket_name, 'allPublic']) + + print('#') + print('# Running test:', test_name) + print('#') + print() + + test_fcn = test_map[test_name] + dirty = not test_fcn(b2_tool, bucket_name) + global_dirty = global_dirty or dirty + + if global_dirty: + print('#' * 70) + print('#') + print('# The last test was run, cleaning up') + print('#') + print('#' * 70) + print() + clean_buckets(b2_tool, bucket_name_prefix) + print() + print("ALL OK") + + +if __name__ == '__main__': + if sys.argv[1:] == ['test']: + del sys.argv[1] + unittest.main() + else: + main() diff --git a/srcpkgs/backblaze-b2/template b/srcpkgs/backblaze-b2/template index 4e9eaf4b744..7956af75d10 100644 --- a/srcpkgs/backblaze-b2/template +++ b/srcpkgs/backblaze-b2/template @@ -1,23 +1,31 @@ # Template file for 'backblaze-b2' pkgname=backblaze-b2 version=1.4.2 -revision=1 +revision=2 archs=noarch -wrksrc="B2_Command_Line_Tool-${version}" -build_style=python2-module +wrksrc="b2-${version}" +build_style=python3-module pycompile_module="b2" -hostmakedepends="python-setuptools" -depends="python-setuptools python-logfury python-futures python-Arrow - python-requests python-six python-tqdm python-b2sdk python-enum34" +hostmakedepends="python3-setuptools" +depends="python3-logfury python3-Arrow python3-requests python3-six + python3-tqdm python-b2sdk" +checkdepends="python3-pytest $depends python3-pyflakes python3-mock + python3-dateutil python3-nose" short_desc="Command Line Interface for Backblaze's B2 storage service" maintainer="Andrea Brancaleoni " license="MIT" homepage="https://github.com/Backblaze/B2_Command_Line_Tool" -distfiles="${homepage}/archive/v${version}.tar.gz" -checksum=2d6382b94af59dcaa44dd546252807e0364d1b61f169584829ebbf82458e7078 +distfiles="${PYPI_SITE}/b/b2/b2-${version}.tar.gz" +checksum=f0a3baf0a94b4c4cc652c5206a03311516742fe87a0e33b51c06adf3eb89c054 replaces="python-b2>=0" provides="python-b2-${version}_${revision}" +post_patch() { + # this files is necessary for do_check + # the files is copied directly from its GitHub's repository + cp "$FILESDIR/test_b2_command_line.py" "$wrksrc" +} + post_install() { # Remove test directory polluting site-packages rm -rf ${DESTDIR}/usr/lib/python*/site-packages/test @@ -28,6 +36,10 @@ post_install() { vlicense LICENSE } +do_check() { + python3 setup.py nosetests +} + python-b2_package() { depends="backblaze-b2>=${version}_${revision}" build_style=meta