From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp61.i.mail.ru (smtp61.i.mail.ru [217.69.128.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 7E653469710 for ; Wed, 18 Nov 2020 12:30:58 +0300 (MSK) From: sergeyb@tarantool.org Date: Wed, 18 Nov 2020 12:30:49 +0300 Message-Id: <8365255bb9eef01293f66d9a7293730fecc49e2b.1605691680.git.sergeyb@tarantool.org> In-Reply-To: <20201116164006.fw3jwes4dwbx7nsd@tkn_work_nb> References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH] Add options for upgrade testing List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: tarantool-patches@dev.tarantool.org, alexander.turenko@tarantool.org From: Sergey Bronnikov Option '--snapshot' specifies a path to snapshot that will be loaded in Tarantool before testing. Option '--disable-schema-upgrade' allows to skip execution of Tarantool upgrade script using error injection mechanism. Closes https://github.com/tarantool/tarantool/issues/4801 --- lib/__init__.py | 4 ++++ lib/app_server.py | 15 +++++++++++++- lib/options.py | 31 +++++++++++++++++++++++++++++ lib/server.py | 9 +++++++++ lib/tarantool_server.py | 44 ++++++++++++++++++++++++++++++++++++----- lib/utils.py | 37 ++++++++++++++++++++++++++++++++++ 6 files changed, 134 insertions(+), 6 deletions(-) diff --git a/lib/__init__.py b/lib/__init__.py index 398439d..a3d1597 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -6,6 +6,7 @@ from lib.options import Options from lib.tarantool_server import TarantoolServer from lib.unittest_server import UnittestServer from utils import warn_unix_sockets_at_start +from utils import test_debug __all__ = ['Options'] @@ -56,6 +57,9 @@ def module_init(): TarantoolServer.find_exe(args.builddir) UnittestServer.find_exe(args.builddir) + is_debug = test_debug(TarantoolServer().version()) + Options().check_schema_upgrade_option(is_debug) + # Init ###### diff --git a/lib/app_server.py b/lib/app_server.py index 2cb8a87..706ae9c 100644 --- a/lib/app_server.py +++ b/lib/app_server.py @@ -9,11 +9,13 @@ from gevent.subprocess import Popen, PIPE from lib.colorer import color_log from lib.preprocessor import TestState from lib.server import Server +from lib.server import DEFAULT_SNAPSHOT_NAME from lib.tarantool_server import Test from lib.tarantool_server import TarantoolServer from lib.tarantool_server import TarantoolStartError from lib.utils import find_port from lib.utils import format_process +from lib.utils import safe_makedirs from lib.utils import warn_unix_socket from test import TestRunGreenlet, TestExecutionError @@ -86,7 +88,12 @@ class AppServer(Server): return os.path.join(self.vardir, file_name) def prepare_args(self, args=[]): - return [os.path.join(os.getcwd(), self.current_test.name)] + args + cli_args = [os.path.join(os.getcwd(), self.current_test.name)] + args + if self.disable_schema_upgrade: + cli_args = [self.binary, '-e', + self.DISABLE_AUTO_UPGRADE] + cli_args + + return cli_args def deploy(self, vardir=None, silent=True, need_init=True): self.vardir = vardir @@ -119,6 +126,12 @@ class AppServer(Server): # cannot check length of path of *.control unix socket created by it. # So for 'app' tests type we don't check *.control unix sockets paths. + if self.snapshot_path: + snapshot_dest = os.path.join(self.vardir, DEFAULT_SNAPSHOT_NAME) + color_log("Copying snapshot {} to {}\n".format( + self.snapshot_path, snapshot_dest)) + shutil.copy(self.snapshot_path, snapshot_dest) + def stop(self, silent): if not self.process: return diff --git a/lib/options.py b/lib/options.py index 47bbc0f..9dcb70e 100644 --- a/lib/options.py +++ b/lib/options.py @@ -209,6 +209,19 @@ class Options: help="""Update or create file with reference output (.result). Default: false.""") + parser.add_argument( + "--snapshot", + dest='snapshot_path', + help="""Path to a Tarantool snapshot that will be + loaded before testing.""") + + parser.add_argument( + "--disable-schema-upgrade", + dest='disable_schema_upgrade', + action="store_true", + default=False, + help="""Disable schema upgrade on testing with snapshots.""") + # XXX: We can use parser.parse_intermixed_args() on # Python 3.7 to understand commands like # ./test-run.py foo --exclude bar baz @@ -227,5 +240,23 @@ class Options: color_stdout(format_str.format(op1, op2), schema='error') check_error = True + snapshot_path = getattr(self.args, 'snapshot_path', '') + if self.args.disable_schema_upgrade and not snapshot_path: + color_stdout("\nOption --disable-schema-upgrade requires --snapshot\n", + schema='error') + check_error = True + + if snapshot_path: + if not os.path.exists(snapshot_path): + color_stdout("\nPath {} not exists\n".format(snapshot_path), schema='error') + check_error = True + else: + self.args.snapshot_path = os.path.abspath(snapshot_path) + if check_error: exit(-1) + + def check_schema_upgrade_option(self, is_debug): + if self.args.disable_schema_upgrade and not is_debug: + color_stdout("Can't disable schema upgrade on release build\n", schema='error') + exit(-1) diff --git a/lib/server.py b/lib/server.py index 321eac7..f0fb03a 100644 --- a/lib/server.py +++ b/lib/server.py @@ -9,12 +9,14 @@ from lib.server_mixins import LLdbMixin from lib.server_mixins import StraceMixin from lib.server_mixins import LuacovMixin from lib.colorer import color_stdout +from lib.options import Options from lib.utils import print_tail_n DEFAULT_CHECKPOINT_PATTERNS = ["*.snap", "*.xlog", "*.vylog", "*.inprogress", "[0-9]*/"] +DEFAULT_SNAPSHOT_NAME = "00000000000000000000.snap" class Server(object): """Server represents a single server instance. Normally, the @@ -24,6 +26,10 @@ class Server(object): DEFAULT_INSPECTOR = 0 TEST_RUN_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + # assert(false) hangs due to gh-4983, added fiber.sleep(0) to workaround it + DISABLE_AUTO_UPGRADE = "require('fiber').sleep(0) \ + assert(box.error.injection.set('ERRINJ_AUTO_UPGRADE', true) == 'ok', \ + 'no such errinj')" @property def vardir(self): @@ -84,6 +90,9 @@ class Server(object): self.inspector_port = int(ini.get( 'inspector_port', self.DEFAULT_INSPECTOR )) + self.testdir = os.path.abspath(os.curdir) + self.disable_schema_upgrade = Options().args.disable_schema_upgrade + self.snapshot_path = Options().args.snapshot_path # filled in {Test,AppTest,LuaTest,PythonTest}.execute() # or passed through execfile() for PythonTest (see diff --git a/lib/tarantool_server.py b/lib/tarantool_server.py index fd102b9..17b6f44 100644 --- a/lib/tarantool_server.py +++ b/lib/tarantool_server.py @@ -26,12 +26,16 @@ from lib.box_connection import BoxConnection from lib.colorer import color_stdout, color_log from lib.preprocessor import TestState from lib.server import Server +from lib.server import DEFAULT_SNAPSHOT_NAME from lib.test import Test from lib.utils import find_port +from lib.utils import extract_schema_from_snapshot from lib.utils import format_process +from lib.utils import safe_makedirs from lib.utils import signame from lib.utils import warn_unix_socket from lib.utils import prefix_each_line +from lib.utils import test_debug from test import TestRunGreenlet, TestExecutionError @@ -609,7 +613,6 @@ class TarantoolServer(Server): } ini.update(_ini) Server.__init__(self, ini, test_suite) - self.testdir = os.path.abspath(os.curdir) self.sourcedir = os.path.abspath(os.path.join(os.path.basename( sys.argv[0]), "..", "..")) self.name = "default" @@ -775,8 +778,29 @@ class TarantoolServer(Server): shutil.copy(os.path.join(self.TEST_RUN_DIR, 'pretest_clean.lua'), self.vardir) + if self.snapshot_path: + # Copy snapshot to the workdir. + # Usually Tarantool looking for snapshots on start in a current directory + # or in a directories that specified in memtx_dir or vinyl_dir box settings. + # Before running test current directory (workdir) passed to a new instance in + # an environment variable TEST_WORKDIR and then .tarantoolctl.in + # adds to it instance_name and set to memtx_dir and vinyl_dir. + (instance_name, _) = os.path.splitext(os.path.basename(self.script)) + instance_dir = os.path.join(self.vardir, instance_name) + safe_makedirs(instance_dir) + snapshot_dest = os.path.join(instance_dir, DEFAULT_SNAPSHOT_NAME) + color_log("Copying snapshot {} to {}\n".format( + self.snapshot_path, snapshot_dest)) + shutil.copy(self.snapshot_path, snapshot_dest) + def prepare_args(self, args=[]): - return [self.ctl_path, 'start', os.path.basename(self.script)] + args + cli_args = [self.ctl_path, 'start', + os.path.basename(self.script)] + args + if self.disable_schema_upgrade: + cli_args = [self.binary, '-e', + self.DISABLE_AUTO_UPGRADE] + cli_args + + return cli_args def pretest_clean(self): # Don't delete snap and logs for 'default' tarantool server @@ -861,6 +885,18 @@ class TarantoolServer(Server): self.admin = CON_SWITCH[self.tests_type]('localhost', port) self.status = 'started' + if not self.ctl_path: + self.ctl_path = self.find_exe(self.builddir) + if self.disable_schema_upgrade: + version = extract_schema_from_snapshot(self.ctl_path, + self.builddir, self.snapshot_path) + current_ver = yaml.safe_load(self.admin.execute( + 'box.space._schema:get{"version"}')) + if version == current_ver: + raise_msg = 'Versions are inequal ({} vs {})'.format( + version, current_ver) + raise Exception(raise_msg) + def crash_detect(self): if self.crash_expected: return @@ -1075,9 +1111,7 @@ class TarantoolServer(Server): print self.test_option_get(option_list_str) def test_debug(self): - if re.findall(r"-Debug", self.test_option_get("-V", True), re.I): - return True - return False + return test_debug(self.test_option_get("-V", True)) @staticmethod def find_tests(test_suite, suite_path): diff --git a/lib/utils.py b/lib/utils.py index cc2f6b7..2aeedc5 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -1,10 +1,13 @@ import os +import re import sys import six import collections import signal +import subprocess import random import fcntl +import json import difflib import time from gevent import socket @@ -264,3 +267,37 @@ def just_and_trim(src, width): if len(src) > width: return src[:width - 1] + '>' return src.ljust(width) + + +def extract_schema_from_snapshot(ctl_path, builddir, snapshot_path): + """ + Extract schema version from snapshot, example of record: + {"HEADER":{"lsn":2,"type":"INSERT","timestamp":1584694286.0031}, + "BODY":{"space_id":272,"tuple":["version",2,3,1]}} + """ + SCHEMA_SPACE_ID = 272 + + ctl_args = [ctl_path, 'cat', snapshot_path, + '--format=json', '--show-system'] + proc = subprocess.Popen(ctl_args, stdout=subprocess.PIPE) + version = None + while True: + line = proc.stdout.readline() + if not line: + break + json_line = json.loads(line.strip()) + query_type = json_line["HEADER"]["type"] + space_id = json_line["BODY"]["space_id"] + if query_type == "INSERT" or query_type == "REPLACE" and space_id == SCHEMA_SPACE_ID: + query_tuple = json_line['BODY']['tuple'] + if query_tuple[0] == 'version': + version = query_tuple + break + + return version + + +def test_debug(version): + if re.findall(r"-Debug", version, re.I): + return True + return False -- 2.25.1