FreeBSD Bugzilla – Attachment 126602 Details for
Bug 170348
[update]: databases/tarantool up to 1.4.7
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
file.diff
file.diff (text/plain), 41.53 KB, created by
Veniamin
on 2012-08-03 10:00:26 UTC
(
hide
)
Description:
file.diff
Filename:
MIME Type:
Creator:
Veniamin
Created:
2012-08-03 10:00:26 UTC
Size:
41.53 KB
patch
obsolete
>diff -ruN tarantool.orig/Makefile tarantool/Makefile >--- tarantool.orig/Makefile 2012-08-03 12:51:13.079221613 +0400 >+++ tarantool/Makefile 2012-08-02 21:32:10.529638998 +0400 >@@ -6,38 +6,54 @@ > # > > PORTNAME= tarantool >-PORTVERSION= 1.3.5 >+PORTVERSION= 1.4.7 > CATEGORIES= databases >-MASTER_SITES= http://launchpadlibrarian.net/71705094/ >-DISTNAME= ${PORTNAME}-${PORTVERSION}-src >+MASTER_SITES= http://tarantool.org/dist/ \ >+ http://fbsd.zlonet.ru/distfiles/ >+DISTNAME= ${PORTNAME}-${PORTVERSION}-${TR_REV}-src > > MAINTAINER= g.veniamin@googlemail.com > COMMENT= Tarantool, is a high performance key/value storage server > > LICENSE= BSD > >-ONLY_FOR_ARCHS= i386 >- >+TR_REV= 32-ga313988 > USE_CMAKE= yes >-ARCH= i386 >+USE_GCC= 4.6+ >+USE_GMAKE= yes >+USE_LDCONFIG= yes >+USE_PERL5= yes > USE_RC_SUBR= ${PORTNAME} > SUB_FILES= pkg-message >+MAN1= tarantool_box.1 > >-.include <bsd.port.pre.mk> >- >-post-patch: >- @${REINPLACE_CMD} -e 's|%%DOCSDIR%%|${DOCSDIR}|g' \ >- ${WRKSRC}/CMakeLists.txt >- @${REINPLACE_CMD} -e 's|%%ETCDIR%%|${ETCDIR}|g' \ >- ${WRKSRC}/test/CMakeLists.txt >- @${RM} ${WRKSRC}/test/lib/server.py \ >- ${WRKSRC}/test/lib/silverbox.py \ >- ${WRKSRC}/test/lib/tarantool_admin.py \ >- ${WRKSRC}/test/lib/tarantool_feeder_server.py \ >- ${WRKSRC}/test/lib/tarantool_server.py \ >- ${WRKSRC}/test/lib/*.orig >+OPTIONS_DEFINE= CLIENT DEBUG STATIC >+CLIENT_DESC= Enable client >+DEBUG_DESC= Enable debug >+STATIC_DESC= Enable static (got core dump) >+ >+OPTIONS_DEFAULT=CLIENT DEBUG >+ >+.include <bsd.port.options.mk> >+ >+.if ${PORT_OPTIONS:MCLIENT} >+CMAKE_ARGS+= -DENABLE_CLIENT=true >+CFLAGS+= "-O0 -g" >+PLIST_SUB+= CLIENT="" >+MAN1+= tarantool.1 >+.else >+PLIST_SUB+= CLIENT="@comment " >+.endif >+ >+.if ${PORT_OPTIONS:MDEBUG} >+CMAKE_ARGS+= -DCMAKE_BUILD_TYPE=RelWithDebugInfo >+.endif >+ >+.if ${PORT_OPTIONS:MSTATIC} >+CMAKE_ARGS+= -DENABLE_STATIC=true >+.endif > > post-install: > @${CAT} ${PKGMESSAGE} > >-.include <bsd.port.post.mk> >+.include <bsd.port.mk> >diff -ruN tarantool.orig/distinfo tarantool/distinfo >--- tarantool.orig/distinfo 2012-08-03 12:51:13.079221613 +0400 >+++ tarantool/distinfo 2012-08-01 14:14:14.250965876 +0400 >@@ -1,2 +1,2 @@ >-SHA256 (tarantool-1.3.5-src.tar.gz) = c78eb302eabac7b6ae04a8eadf8b2819e992d2913cdafe1a86222148982351ec >-SIZE (tarantool-1.3.5-src.tar.gz) = 829327 >+SHA256 (tarantool-1.4.7-32-ga313988-src.tar.gz) = 3ebbf896671da275f27b48d2ce968d310de7663dfb9780c70bab0b003debb88b >+SIZE (tarantool-1.4.7-32-ga313988-src.tar.gz) = 2168920 >diff -ruN tarantool.orig/files/patch-CMakeLists.txt tarantool/files/patch-CMakeLists.txt >--- tarantool.orig/files/patch-CMakeLists.txt 2012-08-03 12:51:13.058669560 +0400 >+++ tarantool/files/patch-CMakeLists.txt 1970-01-01 03:00:00.000000000 +0300 >@@ -1,11 +0,0 @@ >---- CMakeLists.txt.orig 2011-11-25 15:33:08.997444924 +0000 >-+++ CMakeLists.txt 2011-11-25 15:33:30.428593855 +0000 >-@@ -197,7 +197,7 @@ >- add_subdirectory(test) >- >- install (FILES README LICENSE doc/silverbox-protocol.txt >-- DESTINATION doc) >-+ DESTINATION %%DOCSDIR%%) >- >- include (cmake/tarantool_cpack.cmake) >- # >diff -ruN tarantool.orig/files/patch-doc_man_CMakeLists.txt tarantool/files/patch-doc_man_CMakeLists.txt >--- tarantool.orig/files/patch-doc_man_CMakeLists.txt 1970-01-01 03:00:00.000000000 +0300 >+++ tarantool/files/patch-doc_man_CMakeLists.txt 2012-08-01 14:14:14.223296716 +0400 >@@ -0,0 +1,12 @@ >+--- doc/man/CMakeLists.txt.orig 2012-07-27 19:39:47.550688521 +0400 >++++ doc/man/CMakeLists.txt 2012-07-27 19:39:59.834015592 +0400 >+@@ -5,7 +5,7 @@ add_custom_target(man ALL >+ ${CMAKE_SOURCE_DIR}/doc/man/tarantool.pod > ${PROJECT_BINARY_DIR}/doc/man/tarantool.1 >+ ) >+ >+-install(FILES ${PROJECT_BINARY_DIR}/doc/man/tarantool_box.1 DESTINATION "${CMAKE_MAN_DIR}") >++install(FILES ${PROJECT_BINARY_DIR}/doc/man/tarantool_box.1 DESTINATION "${CMAKE_MAN_DIR}/man1") >+ if (ENABLE_CLIENT) >+- install(FILES ${PROJECT_BINARY_DIR}/doc/man/tarantool.1 DESTINATION "${CMAKE_MAN_DIR}") >++ install(FILES ${PROJECT_BINARY_DIR}/doc/man/tarantool.1 DESTINATION "${CMAKE_MAN_DIR}/man1") >+ endif() >diff -ruN tarantool.orig/files/patch-test_CMakeLists.txt tarantool/files/patch-test_CMakeLists.txt >--- tarantool.orig/files/patch-test_CMakeLists.txt 2012-08-03 12:51:13.058669560 +0400 >+++ tarantool/files/patch-test_CMakeLists.txt 1970-01-01 03:00:00.000000000 +0300 >@@ -1,9 +0,0 @@ >---- test/CMakeLists.txt.orig 2011-12-11 16:16:40.594230551 +0000 >-+++ test/CMakeLists.txt 2011-12-11 16:19:44.915010706 +0000 >-@@ -9,5 +9,4 @@ >- >- install (PROGRAMS tarantool DESTINATION bin) >- install (DIRECTORY lib DESTINATION bin) >--install (FILES box/tarantool.cfg box/00000000000000000001.snap >-- DESTINATION bin) >-+install (FILES box/tarantool.cfg DESTINATION %%ETCDIR%%) >diff -ruN tarantool.orig/files/patch-test_box_tarantool.cfg tarantool/files/patch-test_box_tarantool.cfg >--- tarantool.orig/files/patch-test_box_tarantool.cfg 2012-08-03 12:51:13.058669560 +0400 >+++ tarantool/files/patch-test_box_tarantool.cfg 1970-01-01 03:00:00.000000000 +0300 >@@ -1,17 +0,0 @@ >---- test/box/tarantool.cfg.orig 2011-12-13 01:02:02.069760259 +0000 >-+++ test/box/tarantool.cfg 2011-12-13 01:03:52.550055101 +0000 >-@@ -1,11 +1,11 @@ >- slab_alloc_arena = 0.1 >- >--pid_file = "box.pid" >-- >-+pid_file = "/var/run/tarantool.pid" >-+work_dir = "/var/db/tarantool" >- >- # Use -a not -a to work correctly on FreeBSD >- # >--logger="tee -a tarantool.log" >-+logger="cat >> /var/log/tarantool.log" >- >- primary_port = 33013 >- secondary_port = 33014 >diff -ruN tarantool.orig/files/patch-test_lib_sql.g tarantool/files/patch-test_lib_sql.g >--- tarantool.orig/files/patch-test_lib_sql.g 2012-08-03 12:51:13.065848964 +0400 >+++ tarantool/files/patch-test_lib_sql.g 1970-01-01 03:00:00.000000000 +0300 >@@ -1,57 +0,0 @@ >---- test/lib/sql.g.orig 2011-05-14 12:16:32.000000000 +0000 >-+++ test/lib/sql.g 2011-12-13 00:41:37.729004939 +0000 >-@@ -5,10 +5,6 @@ >- >- %% >- >--# The grammar below solely covers the functionality provided by >--# Tarantool binary protocol, from which follow all the >--# limitations. For reference please see doc/box-protocol.txt. >-- >- parser sql: >- >- ignore: '\\s+' >-@@ -25,8 +21,6 @@ >- token WHERE: 'where' >- token VALUES: 'values' >- token SET: 'set' >-- token OR: 'or' >-- token LIMIT: 'limit' >- token END: '\\s*$' >- >- rule sql: (insert {{ stmt = insert }} | >-@@ -37,27 +31,19 @@ >- >- rule insert: INSERT [INTO] ident VALUES value_list >- {{ return sql_ast.StatementInsert(ident, value_list) }} >-- rule update: UPDATE ident SET update_list opt_simple_where >-- {{ return sql_ast.StatementUpdate(ident, update_list, opt_simple_where) }} >-- rule delete: DELETE FROM ident opt_simple_where >-- {{ return sql_ast.StatementDelete(ident, opt_simple_where) }} >-- rule select: SELECT '\*' FROM ident opt_where opt_limit >-- {{ return sql_ast.StatementSelect(ident, opt_where, opt_limit) }} >-+ rule update: UPDATE ident SET update_list opt_where >-+ {{ return sql_ast.StatementUpdate(ident, update_list, opt_where) }} >-+ rule delete: DELETE FROM ident opt_where >-+ {{ return sql_ast.StatementDelete(ident, opt_where) }} >-+ rule select: SELECT '\*' FROM ident opt_where >-+ {{ return sql_ast.StatementSelect(ident, opt_where) }} >- rule ping: PING >- {{ return sql_ast.StatementPing() }} >- rule predicate: ident '=' constant >- {{ return (ident, constant) }} >-- rule opt_simple_where: {{ return None }} >-+ rule opt_where: {{ return None }} >- | WHERE predicate >- {{ return predicate }} >-- rule opt_where: {{ return None }} >-- | WHERE disjunction >-- {{ return disjunction }} >-- rule disjunction: predicate {{ disjunction = [predicate] }} >-- [(OR predicate {{ disjunction.append(predicate) }})+] >-- {{ return disjunction }} >-- rule opt_limit: {{ return 0xffffffff }} >-- | LIMIT NUM {{ return int(NUM) }} >- rule value_list: '\(' expr {{ value_list = [expr] }} >- [("," expr {{ value_list.append(expr) }} )+] >- '\)' {{ return value_list }} >diff -ruN tarantool.orig/files/patch-test_lib_sql.py tarantool/files/patch-test_lib_sql.py >--- tarantool.orig/files/patch-test_lib_sql.py 2012-08-03 12:51:13.057640938 +0400 >+++ tarantool/files/patch-test_lib_sql.py 1970-01-01 03:00:00.000000000 +0300 >@@ -1,92 +0,0 @@ >---- test/lib/sql.py.orig 2011-05-14 12:16:32.000000000 +0000 >-+++ test/lib/sql.py 2011-12-13 00:23:04.673107891 +0000 >-@@ -30,8 +30,6 @@ >- ('WHERE', re.compile('where')), >- ('VALUES', re.compile('values')), >- ('SET', re.compile('set')), >-- ('OR', re.compile('or')), >-- ('LIMIT', re.compile('limit')), >- ('END', re.compile('\\s*$')), >- ] >- def __init__(self, str,*args,**kw): >-@@ -76,16 +74,16 @@ >- ident = self.ident(_context) >- SET = self._scan('SET', context=_context) >- update_list = self.update_list(_context) >-- opt_simple_where = self.opt_simple_where(_context) >-- return sql_ast.StatementUpdate(ident, update_list, opt_simple_where) >-+ opt_where = self.opt_where(_context) >-+ return sql_ast.StatementUpdate(ident, update_list, opt_where) >- >- def delete(self, _parent=None): >- _context = self.Context(_parent, self._scanner, 'delete', []) >- DELETE = self._scan('DELETE', context=_context) >- FROM = self._scan('FROM', context=_context) >- ident = self.ident(_context) >-- opt_simple_where = self.opt_simple_where(_context) >-- return sql_ast.StatementDelete(ident, opt_simple_where) >-+ opt_where = self.opt_where(_context) >-+ return sql_ast.StatementDelete(ident, opt_where) >- >- def select(self, _parent=None): >- _context = self.Context(_parent, self._scanner, 'select', []) >-@@ -94,8 +92,7 @@ >- FROM = self._scan('FROM', context=_context) >- ident = self.ident(_context) >- opt_where = self.opt_where(_context) >-- opt_limit = self.opt_limit(_context) >-- return sql_ast.StatementSelect(ident, opt_where, opt_limit) >-+ return sql_ast.StatementSelect(ident, opt_where) >- >- def ping(self, _parent=None): >- _context = self.Context(_parent, self._scanner, 'ping', []) >-@@ -109,8 +106,8 @@ >- constant = self.constant(_context) >- return (ident, constant) >- >-- def opt_simple_where(self, _parent=None): >-- _context = self.Context(_parent, self._scanner, 'opt_simple_where', []) >-+ def opt_where(self, _parent=None): >-+ _context = self.Context(_parent, self._scanner, 'opt_where', []) >- _token = self._peek('WHERE', 'END', context=_context) >- if _token == 'END': >- return None >-@@ -119,38 +116,6 @@ >- predicate = self.predicate(_context) >- return predicate >- >-- def opt_where(self, _parent=None): >-- _context = self.Context(_parent, self._scanner, 'opt_where', []) >-- _token = self._peek('WHERE', 'LIMIT', 'END', context=_context) >-- if _token != 'WHERE': >-- return None >-- else: # == 'WHERE' >-- WHERE = self._scan('WHERE', context=_context) >-- disjunction = self.disjunction(_context) >-- return disjunction >-- >-- def disjunction(self, _parent=None): >-- _context = self.Context(_parent, self._scanner, 'disjunction', []) >-- predicate = self.predicate(_context) >-- disjunction = [predicate] >-- if self._peek('OR', 'LIMIT', 'END', context=_context) == 'OR': >-- while 1: >-- OR = self._scan('OR', context=_context) >-- predicate = self.predicate(_context) >-- disjunction.append(predicate) >-- if self._peek('OR', 'LIMIT', 'END', context=_context) != 'OR': break >-- return disjunction >-- >-- def opt_limit(self, _parent=None): >-- _context = self.Context(_parent, self._scanner, 'opt_limit', []) >-- _token = self._peek('LIMIT', 'END', context=_context) >-- if _token == 'END': >-- return 0xffffffff >-- else: # == 'LIMIT' >-- LIMIT = self._scan('LIMIT', context=_context) >-- NUM = self._scan('NUM', context=_context) >-- return int(NUM) >-- >- def value_list(self, _parent=None): >- _context = self.Context(_parent, self._scanner, 'value_list', []) >- self._scan("'\\('", context=_context) >diff -ruN tarantool.orig/files/patch-test_lib_sql_ast.py tarantool/files/patch-test_lib_sql_ast.py >--- tarantool.orig/files/patch-test_lib_sql_ast.py 2012-08-03 12:51:13.050440302 +0400 >+++ tarantool/files/patch-test_lib_sql_ast.py 1970-01-01 03:00:00.000000000 +0300 >@@ -1,45 +0,0 @@ >---- test/lib/sql_ast.py.orig 2011-05-14 12:16:32.000000000 +0000 >-+++ test/lib/sql_ast.py 2011-12-13 00:23:04.673107891 +0000 >-@@ -242,22 +242,16 @@ >- class StatementSelect(StatementPing): >- reqeust_type = SELECT_REQUEST_TYPE >- >-- def __init__(self, table_name, where, limit): >-+ def __init__(self, table_name, where): >- self.namespace_no = table_name >-- self.index_no = None >-- self.key_list = [] >-- if not where: >-- self.index_no = 0 >-- self.key_list = ["",] >-+ if where: >-+ (self.index_no, key) = where >-+ self.key = [key] >- else: >-- for (index_no, key) in where: >-- self.key_list.append(key) >-- if self.index_no == None: >-- self.index_no = index_no >-- elif self.index_no != index_no: >-- raise RuntimeError("All key values in a disjunction must refer to the same index") >-+ self.index_no = 0 >-+ self.key = [""] >- self.offset = 0 >-- self.limit = limit >-+ self.limit = 0xffffffff >- >- def pack(self): >- buf = ctypes.create_string_buffer(PACKET_BUF_LEN) >-@@ -266,10 +260,8 @@ >- self.index_no, >- self.offset, >- self.limit, >-- len(self.key_list)) >-- offset = SELECT_REQUEST_FIXED_LEN >-- for key in self.key_list: >-- (buf, offset) = pack_tuple([key], buf, offset) >-+ 1) >-+ (buf, offset) = pack_tuple(self.key, buf, SELECT_REQUEST_FIXED_LEN) >- >- return buf[:offset] >- >diff -ruN tarantool.orig/files/patch-test_lib_tarantool_connection.py tarantool/files/patch-test_lib_tarantool_connection.py >--- tarantool.orig/files/patch-test_lib_tarantool_connection.py 2012-08-03 12:51:13.058669560 +0400 >+++ tarantool/files/patch-test_lib_tarantool_connection.py 1970-01-01 03:00:00.000000000 +0300 >@@ -1,158 +0,0 @@ >---- test/lib/tarantool_connection.py.orig 1970-01-01 00:00:00.000000000 +0000 >-+++ test/lib/tarantool_connection.py 2011-12-13 00:23:04.673107891 +0000 >-@@ -0,0 +1,155 @@ >-+__author__ = "Konstantin Osipov <kostja.osipov@gmail.com>" >-+ >-+# 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 AUTHOR 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 AUTHOR 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. >-+ >-+import socket >-+import sys >-+import string >-+import cStringIO >-+import yaml >-+import re >-+import sql >-+import struct >-+import errno >-+ >-+is_admin_re = re.compile("^\s*(show|save|exec|exit|reload|help)", re.I) >-+ >-+class AdminConnection: >-+ def __init__(self, host, port): >-+ self.host = host >-+ self.port = port >-+ self.is_connected = False >-+ self.stream = cStringIO.StringIO() >-+ >-+ def connect(self): >-+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) >-+ self.socket.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) >-+ self.socket.connect((self.host, self.port)) >-+ self.is_connected = True >-+ >-+ def disconnect(self): >-+ if self.is_connected: >-+ self.socket.close() >-+ self.is_connected = False >-+ >-+ def reconnect(self): >-+ self.disconnect() >-+ self.connect() >-+ >-+ def opt_reconnect(self): >-+ """ On a socket which was disconnected, recv of 0 bytes immediately >-+ returns with no data. On a socket which is alive, it returns EAGAIN. >-+ Make use of this property and detect whether or not the socket is >-+ dead. Reconnect a dead socket, do nothing if the socket is good.""" >-+ try: >-+ if self.socket.recv(0, socket.MSG_DONTWAIT) == '': >-+ self.reconnect() >-+ except socket.error as e: >-+ if e.errno == errno.EAGAIN: >-+ pass >-+ else: >-+ self.reconnect() >-+ >-+ def execute(self, command): >-+ self.opt_reconnect() >-+ return self.execute_no_reconnect(command) >-+ >-+ def execute_no_reconnect(self, command): >-+ self.socket.sendall(command) >-+ >-+ bufsiz = 4096 >-+ res = "" >-+ >-+ while True: >-+ buf = self.socket.recv(bufsiz) >-+ if not buf: >-+ break >-+ res = res + buf; >-+ if (res.rfind("\r\n...\r\n") >= 0): >-+ break >-+ >-+ # validate yaml by parsing it >-+ yaml.load(res) >-+ >-+ return res >-+ >-+ def write(self, fragment): >-+ """This is to support print >> admin, "command" syntax. >-+ For every print statement, write is invoked twice: one to >-+ write the command itself, and another to write \n. We should >-+ accumulate all writes until we receive \n. When we receive it, >-+ we execute the command, and rewind the stream.""" >-+ >-+ newline_pos = fragment.rfind("\n") >-+ while newline_pos >= 0: >-+ self.stream.write(fragment[:newline_pos+1]) >-+ statement = self.stream.getvalue() >-+ sys.stdout.write(statement) >-+ sys.stdout.write(self.execute(statement)) >-+ fragment = fragment[newline_pos+1:] >-+ newline_pos = fragment.rfind("\n") >-+ self.stream.seek(0) >-+ self.stream.truncate() >-+ >-+ self.stream.write(fragment) >-+ >-+ def __enter__(self): >-+ self.connect() >-+ return self >-+ >-+ def __exit__(self, type, value, tb): >-+ self.disconnect() >-+ >-+class DataConnection(AdminConnection): >-+ >-+ def recvall(self, length): >-+ res = "" >-+ while len(res) < length: >-+ buf = self.socket.recv(length - len(res)) >-+ res = res + buf >-+ return res >-+ >-+ def execute_no_reconnect(self, command): >-+ statement = sql.parse("sql", command) >-+ if statement == None: >-+ return "You have an error in your SQL syntax\n" >-+ >-+ payload = statement.pack() >-+ header = struct.pack("<lll", statement.reqeust_type, len(payload), 0) >-+ >-+ self.socket.sendall(header) >-+ if len(payload): >-+ self.socket.sendall(payload) >-+ >-+ IPROTO_HEADER_SIZE = 12 >-+ >-+ header = self.recvall(IPROTO_HEADER_SIZE) >-+ >-+ response_len = struct.unpack("<lll", header)[1] >-+ >-+ if response_len: >-+ response = self.recvall(response_len) >-+ else: >-+ response = None >-+ >-+ return statement.unpack(response) + "\n" >-+ >diff -ruN tarantool.orig/files/patch-test_lib_tarantool_silverbox_server.py tarantool/files/patch-test_lib_tarantool_silverbox_server.py >--- tarantool.orig/files/patch-test_lib_tarantool_silverbox_server.py 2012-08-03 12:51:13.073049041 +0400 >+++ tarantool/files/patch-test_lib_tarantool_silverbox_server.py 1970-01-01 03:00:00.000000000 +0300 >@@ -1,266 +0,0 @@ >---- test/lib/tarantool_silverbox_server.py.orig 2011-05-14 12:16:32.000000000 +0000 >-+++ test/lib/tarantool_silverbox_server.py 2011-12-13 00:23:04.673107891 +0000 >-@@ -1,35 +1,234 @@ >-+import os >-+import stat >- import shutil >- import subprocess >--import yaml >--import ConfigParser >--from tarantool_server import TarantoolServer, TarantoolConfigFile >--from tarantool_admin import TarantoolAdmin >--from silverbox import Silverbox >-- >--class TarantoolSilverboxServer(TarantoolServer): >-- def __new__(cls, core="tarantool", module="silverbox"): >-- return TarantoolServer.__new__(cls) >-- >-- def __init__(self, core="tarantool", module="silverbox"): >-- TarantoolServer.__init__(self, core, module) >-- >-- def configure(self, config): >-- TarantoolServer.configure(self, config) >-- with open(self.config) as fp: >-- dummy_section_name = "tarantool" >-- config = ConfigParser.ConfigParser() >-- config.readfp(TarantoolConfigFile(fp, dummy_section_name)) >-- self.primary_port = int(config.get(dummy_section_name, "primary_port")) >-- self.admin_port = int(config.get(dummy_section_name, "admin_port")) >-- self.port = self.admin_port >-- self.admin = TarantoolAdmin("localhost", self.admin_port) >-- self.sql = Silverbox("localhost", self.primary_port) >-- >-- def init(self): >--# init storage >-- subprocess.check_call([self.binary, "--init_storage"], >-- cwd = self.vardir, >-+import pexpect >-+import sys >-+import signal >-+import time >-+import socket >-+import daemon >-+import glob >-+ >-+def wait_until_connected(host, port): >-+ """Wait until the server is started and accepting connections""" >-+ is_connected = False >-+ while not is_connected: >-+ try: >-+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) >-+ sock.connect((host, port)) >-+ is_connected = True >-+ sock.close() >-+ except socket.error as e: >-+ time.sleep(0.001) >-+ >-+ >-+def prepare_gdb(args): >-+ """Prepare server startup arguments to run under gdb.""" >-+ if "TERM" in os.environ: >-+ term = os.environ["TERM"] >-+ else: >-+ term = "xterm" >-+ >-+ if term not in ["xterm", "rxvt", "urxvt", "gnome-terminal", "konsole"]: >-+ raise RuntimeError("--gdb: unsupported terminal {0}".format(term)) >-+ >-+ args = [ term, "-e ", "gdb", "-ex", "break main", "-ex", "run"] + args >-+ return args >-+ >-+ >-+def prepare_valgrind(args, valgrind_opts): >-+ "Prepare server startup arguments to run under valgrind." >-+ args = ([ "valgrind", "--log-file=valgrind.log", "--quiet" ] + >-+ valgrind_opts.split(" ") + args) >-+ return args >-+ >-+ >-+def check_tmpfs_exists(): >-+ return os.uname()[0] in 'Linux' and os.path.isdir("/dev/shm") >-+ >-+def create_tmpfs_vardir(vardir): >-+ os.mkdir(os.path.join("/dev/shm", vardir)) >-+ os.symlink(os.path.join("/dev/shm", vardir), vardir) >-+ >-+class TarantoolSilverboxServer: >-+ """Server represents a single server instance. Normally, the >-+ program operates with only one server, but in future we may add >-+ replication slaves. The server is started once at the beginning >-+ of each suite, and stopped at the end.""" >-+ >-+ def __init__(self, args, suite_ini): >-+ """Set server options: path to configuration file, pid file, exe, etc.""" >-+ self.args = args >-+ self.suite_ini = suite_ini >-+ self.path_to_pidfile = os.path.join(args.vardir, suite_ini["pidfile"]) >-+ self.path_to_exe = None >-+ self.abspath_to_exe = None >-+ self.is_started = False >-+ >-+ def install(self, silent = False): >-+ """Start server instance: check if the old one exists, kill it >-+ if necessary, create necessary directories and files, start >-+ the server. The server working directory is taken from 'vardir', >-+ specified in the prgoram options. >-+ Currently this is implemented for tarantool_silverbox only.""" >-+ >-+ vardir = self.args.vardir >-+ >-+ if not silent: >-+ print "Installing the server..." >-+ >-+ if self.path_to_exe == None: >-+ self.path_to_exe = self.find_exe() >-+ self.abspath_to_exe = os.path.abspath(self.path_to_exe) >-+ >-+ if not silent: >-+ print " Found executable at " + self.path_to_exe + "." >-+ >-+ if not silent: >-+ print " Creating and populating working directory in " +\ >-+ vardir + "..." >-+ >-+ if os.access(vardir, os.F_OK): >-+ if not silent: >-+ print " Found old vardir, deleting..." >-+ self.kill_old_server() >-+ for filename in (glob.glob(os.path.join(vardir, "*.snap")) + >-+ glob.glob(os.path.join(vardir, "*.inprogress")) + >-+ glob.glob(os.path.join(vardir, "*.xlog")) + >-+ glob.glob(os.path.join(vardir, "*.cfg")) + >-+ glob.glob(os.path.join(vardir, "*.log")) + >-+ glob.glob(os.path.join(vardir, "*.core.*")) + >-+ glob.glob(os.path.join(vardir, "core"))): >-+ os.remove(filename) >-+ else: >-+ if (self.args.mem == True and check_tmpfs_exists() and >-+ os.path.basename(vardir) == vardir): >-+ create_tmpfs_vardir(vardir) >-+ else: >-+ os.mkdir(vardir) >-+ >-+ shutil.copy(self.suite_ini["config"], self.args.vardir) >-+ >-+ subprocess.check_call([self.abspath_to_exe, "--init_storage"], >-+ cwd = self.args.vardir, >- # catch stdout/stderr to not clutter output >- stdout = subprocess.PIPE, >- stderr = subprocess.PIPE) >- >-+ p = subprocess.Popen([self.abspath_to_exe, "--version"], >-+ cwd = self.args.vardir, >-+ stdout = subprocess.PIPE) >-+ version = p.stdout.read().rstrip() >-+ p.wait() >-+ >-+ if not silent: >-+ print "Starting {0} {1}.".format(os.path.basename(self.abspath_to_exe), >-+ version) >-+ >-+ def start(self, silent = False): >-+ >-+ if self.is_started: >-+ if not silent: >-+ print "The server is already started." >-+ return >-+ >-+ if not silent: >-+ print "Starting the server..." >-+ >-+ args = [self.abspath_to_exe] >-+ >-+ if (self.args.start_and_exit and >-+ not self.args.valgrind and not self.args.gdb): >-+ args.append("--daemonize") >-+ if self.args.gdb: >-+ args = prepare_gdb(args) >-+ elif self.args.valgrind: >-+ args = prepare_valgrind(args, self.args.valgrind_opts) >-+ >-+ if self.args.start_and_exit and self.args.valgrind: >-+ pid = os.fork() >-+ if pid > 0: >-+ os.wait() >-+ else: >-+ with daemon.DaemonContext(working_directory = self.args.vardir): >-+ os.execvp(args[0], args) >-+ else: >-+ self.server = pexpect.spawn(args[0], args[1:], cwd = self.args.vardir) >-+ if self.args.start_and_exit: >-+ self.server.wait() >-+ >-+# wait until the server is connectedk >-+ if self.args.gdb and self.args.start_and_exit: >-+ time.sleep(1) >-+ elif not self.args.start_and_exit and not self.args.gdb: >-+ self.server.expect_exact("entering event loop") >-+ else: >-+ wait_until_connected(self.suite_ini["host"], self.suite_ini["port"]) >-+ >-+# Set is_started flag, to nicely support cleanup during an exception. >-+ self.is_started = True >-+ >-+ >-+ def stop(self, silent = False): >-+ """Stop server instance. Do nothing if the server is not started, >-+ to properly shut down the server in case of an exception during >-+ start up.""" >-+ if self.is_started: >-+ if not silent: >-+ print "Stopping the server..." >-+ if self.args.gdb: >-+ self.kill_old_server(True) >-+ self.server.terminate() >-+ self.server.expect(pexpect.EOF) >-+ self.is_started = False >-+ elif not silent: >-+ print "The server is not started." >-+ >-+ def restart(self): >-+ self.stop(True) >-+ self.start(True) >-+ >-+ def test_option(self, option_list_str): >-+ args = [self.abspath_to_exe] + option_list_str.split() >-+ print " ".join([os.path.basename(self.abspath_to_exe)] + args[1:]) >-+ output = subprocess.Popen(args, >-+ cwd = self.args.vardir, >-+ stdout = subprocess.PIPE, >-+ stderr = subprocess.STDOUT).stdout.read() >-+ print output >-+ >-+ >-+ def find_exe(self): >-+ """Locate server executable in the bindir. We just take >-+ the first thing looking like an exe in there.""" >-+ >-+ print " Looking for server binary in {0}...".format(self.args.bindir) >-+ if (os.access(self.args.bindir, os.F_OK) == False or >-+ stat.S_ISDIR(os.stat(self.args.bindir).st_mode) == False): >-+ raise RuntimeError("Directory " + self.args.bindir + " doesn't exist") >-+ >-+ for f in os.listdir(self.args.bindir): >-+ f = os.path.join(self.args.bindir, f) >-+ st_mode = os.stat(f).st_mode >-+ if stat.S_ISREG(st_mode) and st_mode & stat.S_IXUSR: >-+ return f >-+ >-+ raise RuntimeError("Can't find server executable in " + self.args.bindir) >-+ >-+ def kill_old_server(self, silent = False): >-+ """Kill old server instance if it exists.""" >-+ if os.access(self.path_to_pidfile, os.F_OK) == False: >-+ return # Nothing to do >-+ pid = 0 >-+ with open(self.path_to_pidfile) as f: >-+ pid = int(f.read()) >-+ if not silent: >-+ print " Found old server, pid {0}, killing...".format(pid) >-+ try: >-+ os.kill(pid, signal.SIGTERM) >-+ while os.kill(pid, 0) != -1: >-+ time.sleep(0.001) >-+ except OSError: >-+ pass >-+ >diff -ruN tarantool.orig/files/patch-test_lib_test_suite.py tarantool/files/patch-test_lib_test_suite.py >--- tarantool.orig/files/patch-test_lib_test_suite.py 2012-08-03 12:51:13.074077663 +0400 >+++ tarantool/files/patch-test_lib_test_suite.py 1970-01-01 03:00:00.000000000 +0300 >@@ -1,210 +0,0 @@ >---- test/lib/test_suite.py.orig 2011-05-14 12:16:32.000000000 +0000 >-+++ test/lib/test_suite.py 2011-12-13 00:23:04.673107891 +0000 >-@@ -10,13 +10,21 @@ >- import filecmp >- import shlex >- import time >--from server import Server >-+from tarantool_silverbox_server import TarantoolSilverboxServer >-+from tarantool_connection import AdminConnection, DataConnection >- import tarantool_preprocessor >- import re >- import cStringIO >- import string >- import traceback >- >-+class TestRunException(RuntimeError): >-+ """A common exception to use across the program.""" >-+ def __init__(self, message): >-+ self.message = message >-+ def __str__(self): >-+ return self.message >-+ >- class FilteredStream: >- """Helper class to filter .result file output""" >- def __init__(self, filename): >-@@ -66,12 +74,12 @@ >- """Initialize test properties: path to test file, path to >- temporary result file, path to the client program, test status.""" >- self.name = name >-- self.args = args >-- self.suite_ini = suite_ini >- self.result = name.replace(".test", ".result") >-- self.tmp_result = os.path.join(self.args.vardir, >-+ self.tmp_result = os.path.join(suite_ini["vardir"], >- os.path.basename(self.result)) >- self.reject = name.replace(".test", ".reject") >-+ self.args = args >-+ self.suite_ini = suite_ini >- self.is_executed = False >- self.is_executed_ok = None >- self.is_equal_result = None >-@@ -81,7 +89,7 @@ >- """Return true if this test was run successfully.""" >- return self.is_executed and self.is_executed_ok and self.is_equal_result >- >-- def run(self, server): >-+ def run(self): >- """Execute the test assuming it's a python program. >- If the test aborts, print its output to stdout, and raise >- an exception. Else, comprare result and reject files. >-@@ -91,10 +99,18 @@ >- >- diagnostics = "unknown" >- save_stdout = sys.stdout >-+ admin = AdminConnection(self.suite_ini["host"], >-+ self.suite_ini["admin_port"]) >-+ sql = DataConnection(self.suite_ini["host"], >-+ self.suite_ini["port"]) >-+ server = self.suite_ini["server"] >- try: >-+ admin.connect() >-+ sql.connect() >- sys.stdout = FilteredStream(self.tmp_result) >-- stdout_fileno = sys.stdout.stream.fileno() >-- execfile(self.name, dict(locals(), **server.__dict__)) >-+ server = self.suite_ini["server"] >-+ vardir = self.suite_ini["vardir"] >-+ execfile(self.name, globals(), locals()) >- self.is_executed_ok = True >- except Exception as e: >- traceback.print_exc(e) >-@@ -103,6 +119,8 @@ >- if sys.stdout and sys.stdout != save_stdout: >- sys.stdout.close() >- sys.stdout = save_stdout; >-+ admin.disconnect() >-+ sql.disconnect() >- >- self.is_executed = True >- >-@@ -111,7 +129,7 @@ >- >- if self.args.valgrind: >- self.is_valgrind_clean = \ >-- check_valgrind_log(server.valgrind_log) == False >-+ check_valgrind_log(self.suite_ini["valgrind_log"]) == False >- >- if self.is_executed_ok and self.is_equal_result and self.is_valgrind_clean: >- print "[ pass ]" >-@@ -133,12 +151,12 @@ >- where = ": wrong test output" >- elif not self.is_valgrind_clean: >- os.remove(self.reject) >-- self.print_diagnostics(server.valgrind_log, >-+ self.print_diagnostics(self.suite_ini["valgrind_log"], >- "Test failed! Last 10 lines of valgrind.log:") >- where = ": there were warnings in valgrind.log" >- >-- if not self.args.is_force: >-- raise RuntimeError("Failed to run test " + self.name + where) >-+ if not self.suite_ini["is_force"]: >-+ raise TestRunException("Failed to run test " + self.name + where) >- >- >- def print_diagnostics(self, logfile, message): >-@@ -167,6 +185,20 @@ >- for line in diff: >- sys.stdout.write(line) >- >-+class TarantoolConfigFile: >-+ """ConfigParser can't read files without sections, work it around""" >-+ def __init__(self, fp, section_name): >-+ self.fp = fp >-+ self.section_name = "[" + section_name + "]" >-+ def readline(self): >-+ if self.section_name: >-+ section_name = self.section_name >-+ self.section_name = None >-+ return section_name >-+ # tarantool.cfg puts string values in quote >-+ return self.fp.readline().replace("\"", '') >-+ >-+ >- class TestSuite: >- """Each test suite contains a number of related tests files, >- located in the same directory on disk. Each test file has >-@@ -186,13 +218,15 @@ >- self.args = args >- self.tests = [] >- self.ini = {} >-- >-- self.ini["core"] = "tarantool" >-- self.ini["module"] = "silverbox" >-+ self.ini["suite_path"] = suite_path >-+ self.ini["host"] = "localhost" >-+ self.ini["is_force"] = self.args.is_force >-+ self.ini["vardir"] = args.vardir >-+ self.ini["valgrind_log"] = os.path.join(args.vardir, "valgrind.log") >- >- if os.access(suite_path, os.F_OK) == False: >-- raise RuntimeError("Suite \"" + suite_path +\ >-- "\" doesn't exist") >-+ raise TestRunException("Suite \"" + suite_path +\ >-+ "\" doesn't exist") >- >- # read the suite config >- config = ConfigParser.ConfigParser() >-@@ -203,6 +237,16 @@ >- self.ini["disabled"] = dict.fromkeys(self.ini["disabled"].split(" ")) >- else: >- self.ini["disabled"] = dict() >-+# import the necessary module for test suite client >-+ >-+# now read the server config, we need some properties from it >-+ >-+ with open(self.ini["config"]) as fp: >-+ dummy_section_name = "tarantool_silverbox" >-+ config.readfp(TarantoolConfigFile(fp, dummy_section_name)) >-+ self.ini["pidfile"] = config.get(dummy_section_name, "pid_file") >-+ self.ini["admin_port"] = int(config.get(dummy_section_name, "admin_port")) >-+ self.ini["port"] = int(config.get(dummy_section_name, "primary_port")) >- >- print "Collecting tests in \"" + suite_path + "\": " +\ >- self.ini["description"] + "." >-@@ -216,17 +260,9 @@ >- def run_all(self): >- """For each file in the test suite, run client program >- assuming each file represents an individual test.""" >-- try: >-- server = Server(self.ini["core"], self.ini["module"]) >-- except Exception as e: >-- print e >-- raise RuntimeError("Unknown server: core = {0}, module = {1}".format( >-- self.ini["core"], self.ini["module"])) >-- server.deploy(self.ini["config"], >-- server.find_exe(self.args.builddir, silent=False), >-- self.args.vardir, >-- self.args.mem, self.args.start_and_exit, self.args.gdb, self.args.valgrind, >-- silent=False) >-+ server = TarantoolSilverboxServer(self.args, self.ini) >-+ server.install() >-+ server.start() >- if self.args.start_and_exit: >- print " Start and exit requested, exiting..." >- exit(0) >-@@ -247,7 +283,7 @@ >- if os.path.basename(test.name) in self.ini["disabled"]: >- print "[ skip ]" >- else: >-- test.run(server) >-+ test.run() >- if not test.passed(): >- failed_tests.append(test.name) >- >-@@ -255,11 +291,9 @@ >- if len(failed_tests): >- print "Failed {0} tests: {1}.".format(len(failed_tests), >- ", ".join(failed_tests)) >-- server.stop(silent=False) >-- server.cleanup() >-+ server.stop(); >- >-- if self.args.valgrind and check_valgrind_log(server.valgrind_log): >-+ if self.args.valgrind and check_valgrind_log(self.ini["valgrind_log"]): >- print " Error! There were warnings/errors in valgrind log file:" >-- print_tail_n(server.valgrind_log, 20) >-- return 1 >-- return len(failed_tests) >-+ print_tail_n(self.ini["valgrind_log"], 20) >-+ >diff -ruN tarantool.orig/files/patch-test_tarantool tarantool/files/patch-test_tarantool >--- tarantool.orig/files/patch-test_tarantool 2012-08-03 12:51:13.065848964 +0400 >+++ tarantool/files/patch-test_tarantool 1970-01-01 03:00:00.000000000 +0300 >@@ -1,30 +0,0 @@ >---- test/tarantool.orig 2011-05-14 12:16:32.000000000 +0000 >-+++ test/tarantool 2011-12-13 01:12:12.696699437 +0000 >-@@ -1,4 +1,4 @@ >--#! /usr/bin/python >-+#! /usr/bin/env python >- """A simplistic client for tarantool/silverbox: administrative >- console and SQL client. >- >-@@ -32,8 +32,8 @@ >- import socket >- import sys >- import string >--from lib.tarantool_admin import TarantoolAdmin, is_admin_re >--from lib.box import Box >-+from lib.tarantool_connection import AdminConnection, DataConnection, \ >-+ is_admin_re >- >- class Options: >- def __init__(self): >-@@ -94,8 +94,8 @@ >- def main(): >- init_readline_history() >- options = Options() >-- admin_con = TarantoolAdmin(options.args.host, options.args.admin_port) >-- data_con = Box(options.args.host, options.args.port) >-+ admin_con = AdminConnection(options.args.host, options.args.admin_port) >-+ data_con = DataConnection(options.args.host, options.args.port) >- try: >- admin_con.connect() >- data_con.connect() >diff -ruN tarantool.orig/files/pkg-message.in tarantool/files/pkg-message.in >--- tarantool.orig/files/pkg-message.in 2012-08-03 12:51:13.058669560 +0400 >+++ tarantool/files/pkg-message.in 2012-08-01 14:14:14.225353961 +0400 >@@ -3,7 +3,7 @@ > # > # After install you'll need init storage: > # >-# %%PREFIX%%/bin/tarantool_silverbox --init-storage \ >+# %%PREFIX%%/bin/tarantool_box --init-storage \ > # -c %%ETCDIR%%/tarantool.cfg > # > # >diff -ruN tarantool.orig/files/tarantool.in tarantool/files/tarantool.in >--- tarantool.orig/files/tarantool.in 2012-08-03 12:51:13.050440302 +0400 >+++ tarantool/files/tarantool.in 2012-08-01 14:14:14.219193122 +0400 >@@ -20,7 +20,7 @@ > tarantool_enable=${tarantool_enable:-"NO"} > tarantool_config=${tarantool_config:-"%%ETCDIR%%/$name.cfg"} > >-command="%%PREFIX%%/bin/tarantool_silverbox" >+command="%%PREFIX%%/bin/tarantool_box" > command_args="--daemonize --config ${tarantool_config}" > pidfile="/var/run/$name.pid" > >diff -ruN tarantool.orig/pkg-plist tarantool/pkg-plist >--- tarantool.orig/pkg-plist 2012-08-03 12:51:13.079221613 +0400 >+++ tarantool/pkg-plist 2012-08-01 14:14:14.235639905 +0400 >@@ -1,21 +1,53 @@ >-bin/lib/__init__.py >-bin/lib/sql.g >-bin/lib/sql.py >-bin/lib/sql_ast.py >-bin/lib/tarantool_connection.py >-bin/lib/tarantool_preprocessor.py >-bin/lib/tarantool_silverbox_server.py >-bin/lib/test_suite.py >-bin/lib/yapps/__init__.py >-bin/lib/yapps/runtime.py >-bin/tarantool >-bin/tarantool_feeder >-bin/tarantool_silverbox >-%%DOCSDIR%%/LICENSE >+include/tarantool/tnt_queue.h >+include/tarantool/tnt_utf8.h >+include/tarantool/tnt_mem.h >+include/tarantool/tnt_tuple.h >+include/tarantool/tnt_update.h >+include/tarantool/tnt_insert.h >+include/tarantool/tnt_opt.h >+include/tarantool/tnt_call.h >+include/tarantool/tnt_xlog.h >+include/tarantool/tnt_iter.h >+include/tarantool/tnt_buf.h >+include/tarantool/tnt_io.h >+include/tarantool/tnt_lex.h >+include/tarantool/tnt_net.h >+include/tarantool/tnt_select.h >+include/tarantool/tnt_proto.h >+include/tarantool/tnt_enc.h >+include/tarantool/tnt_delete.h >+include/tarantool/tnt_sql.h >+include/tarantool/tnt_ping.h >+include/tarantool/tnt_stream.h >+include/tarantool/tnt_request.h >+include/tarantool/tnt_reply.h >+include/tarantool/tnt_rpl.h >+include/tarantool/tnt_iob.h >+include/tarantool/tnt.h >+var/lib/tarantool/00000000000000000001.snap >+etc/tarantool.cfg >+bin/tarantool_box >+%%CLIENT%%bin/tarantool >+lib/libtarantoolnet.so.1.1 >+lib/libtarantoolrpl.so.1 >+lib/libtarantoolnet.a >+lib/libtarantoolnet.so >+lib/libtarantool.a >+lib/libtarantoolnet.so.1 >+lib/libtarantoolrpl.so >+lib/libtarantoolsql.so.1.1 >+lib/libtarantoolsql.a >+lib/libtarantoolsql.so.1 >+lib/libtarantoolrpl.so.1.1 >+lib/libtarantool.so.1 >+lib/libtarantoolsql.so >+lib/libtarantool.so.1.1 >+lib/libtarantoolrpl.a >+lib/libtarantool.so > %%DOCSDIR%%/README >-%%DOCSDIR%%/silverbox-protocol.txt >-%%ETCDIR%%/tarantool.cfg >+%%DOCSDIR%%/box-protocol.txt >+%%DOCSDIR%%/LICENSE >+@dirrm var/lib/tarantool >+@dirrm var/lib > @dirrm %%DOCSDIR%% >-@dirrm %%ETCDIR%% >-@dirrm bin/lib/yapps >-@dirrm bin/lib >+@dirrm include/tarantool
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 170348
: 126602 |
126603