diff --git a/CHANGELOG.txt b/CHANGELOG.txt index c163ac52616..a35f937c717 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -88,8 +88,13 @@ master: (doi: 10.5281/zenodo.165135) get_timing_and_data_quality() function obsolete which is thus deprecated and will be removed with the next release. The get_flags() function is also much faster. (see #1141) + * Always hook up the libmseed logging to its Python counterpart to avoid + some rare segfaults. (see #1658) * Update to libmseed v2.19.2 (see #1703). * Correctly read MiniSEED files with a data offset of 48 bytes (see #1540). + * InternalMSEEDReadingError now called InternalMSEEDError and + InternalMSEEDReadingWarning now called of InternalMSEEDWarning as both + can now also be raised in non-reading contexts (see #1658). - obspy.io.nlloc: * Set preferred origin of event (see #1570) - obspy.io.nordic: diff --git a/obspy/io/mseed/__init__.py b/obspy/io/mseed/__init__.py index 14f02f7dd29..8ce135acea9 100644 --- a/obspy/io/mseed/__init__.py +++ b/obspy/io/mseed/__init__.py @@ -146,11 +146,11 @@ from future.builtins import * # NOQA -class InternalMSEEDReadingError(Exception): +class InternalMSEEDError(Exception): pass -class InternalMSEEDReadingWarning(UserWarning): +class InternalMSEEDWarning(UserWarning): pass diff --git a/obspy/io/mseed/core.py b/obspy/io/mseed/core.py index 582870138b0..2ec9d8d895f 100644 --- a/obspy/io/mseed/core.py +++ b/obspy/io/mseed/core.py @@ -17,7 +17,7 @@ from obspy import Stream, Trace, UTCDateTime from obspy.core.util import NATIVE_BYTEORDER -from . import util, InternalMSEEDReadingError, InternalMSEEDReadingWarning +from . import util, InternalMSEEDError from .headers import (DATATYPES, ENCODINGS, HPTERROR, HPTMODULUS, SAMPLETYPE, SEED_CONTROL_HEADERS, UNSUPPORTED_ENCODINGS, VALID_CONTROL_HEADERS, VALID_RECORD_LENGTHS, Selections, @@ -371,48 +371,34 @@ def allocate_data(samplecount, sampletype): # it hopefully works on 32 and 64 bit systems. alloc_data = C.CFUNCTYPE(C.c_longlong, C.c_int, C.c_char)(allocate_data) - # Collect exceptions. They cannot be raised in the callback as they - # could never be caught then. They are collected an raised later on. - _errs_and_warnings = [] - - def log_error_or_warning(msg): - msg = msg.decode() - if msg.startswith("ERROR: "): - _errs_and_warnings.append( - InternalMSEEDReadingError(msg[7:].strip())) - if msg.startswith("INFO: "): - msg = msg[6:].strip() + try: + verbose = int(verbose) + except Exception: + verbose = 0 + + clibmseed.verbose = bool(verbose) + try: + lil = clibmseed.readMSEEDBuffer( + bfr_np, buflen, selections, C.c_int8(unpack_data), + reclen, C.c_int8(verbose), C.c_int8(details), header_byteorder, + alloc_data) + except InternalMSEEDError as e: + msg = e.args[0] + if offset and offset in str(e): # Append the offset of the full SEED header if necessary. That way # the C code does not have to deal with it. if offset and "offset" in msg: - msg = ("%s The file contains a %i byte dataless part at the " + msg = ("%s\nThe file contains a %i byte dataless part at the " "beginning. Make sure to add that to the reported " "offset to get the actual location in the file." % ( msg, offset)) - _errs_and_warnings.append((msg, InternalMSEEDReadingWarning)) - - diag_print = C.CFUNCTYPE(C.c_void_p, C.c_char_p)(log_error_or_warning) - - def log_message(msg): - print(msg[6:].strip()) - log_print = C.CFUNCTYPE(C.c_void_p, C.c_char_p)(log_message) - - try: - verbose = int(verbose) - except Exception: - verbose = 0 - - lil = clibmseed.readMSEEDBuffer( - bfr_np, buflen, selections, C.c_int8(unpack_data), - reclen, C.c_int8(verbose), C.c_int8(details), header_byteorder, - alloc_data, diag_print, log_print) - - for _i in _errs_and_warnings: - if isinstance(_i, InternalMSEEDReadingError): - raise _i - warnings.warn(*_i) + raise InternalMSEEDError(msg) + else: + raise + finally: + # Make sure to reset the verbosity. + clibmseed.verbose = True - # XXX: Check if the freeing works. del selections traces = [] diff --git a/obspy/io/mseed/headers.py b/obspy/io/mseed/headers.py index 3a4558c971d..d05f98dc228 100644 --- a/obspy/io/mseed/headers.py +++ b/obspy/io/mseed/headers.py @@ -8,9 +8,11 @@ from future.utils import native_str import ctypes as C +import warnings import numpy as np +from . import InternalMSEEDError, InternalMSEEDWarning from obspy.core.util.libnames import _load_cdll @@ -18,9 +20,6 @@ ENDIAN = {0: '<', 1: '>'} -# Import shared libmseed -clibmseed = _load_cdll("mseed") - # XXX: Do we still support Python 2.4 ???? # Figure out Py_ssize_t (PEP 353). @@ -482,89 +481,6 @@ class UDIFF(C.Union): ] -# Declare function of libmseed library, argument parsing -clibmseed.mst_init.argtypes = [C.POINTER(MSTrace)] -clibmseed.mst_init.restype = C.POINTER(MSTrace) - -clibmseed.mst_free.argtypes = [C.POINTER(C.POINTER(MSTrace))] -clibmseed.mst_free.restype = C.c_void_p - -clibmseed.mst_initgroup.argtypes = [C.POINTER(MSTraceGroup)] -clibmseed.mst_initgroup.restype = C.POINTER(MSTraceGroup) - -clibmseed.mst_freegroup.argtypes = [C.POINTER(C.POINTER(MSTraceGroup))] -clibmseed.mst_freegroup.restype = C.c_void_p - -clibmseed.msr_init.argtypes = [C.POINTER(MSRecord)] -clibmseed.msr_init.restype = C.POINTER(MSRecord) - -clibmseed.ms_readmsr_r.argtypes = [ - C.POINTER(C.POINTER(MSFileParam)), C.POINTER(C.POINTER(MSRecord)), - C.c_char_p, C.c_int, C.POINTER(Py_ssize_t), C.POINTER(C.c_int), C.c_short, - C.c_short, C.c_short] -clibmseed.ms_readmsr_r.restypes = C.c_int - -clibmseed.ms_readtraces.argtypes = [ - C.POINTER(C.POINTER(MSTraceGroup)), C.c_char_p, C.c_int, C.c_double, - C.c_double, C.c_short, C.c_short, C.c_short, C.c_short] -clibmseed.ms_readtraces.restype = C.c_int - -clibmseed.ms_readtraces_timewin.argtypes = [ - C.POINTER(C.POINTER(MSTraceGroup)), C.c_char_p, C.c_int, C.c_double, - C.c_double, C.c_int64, C.c_int64, C.c_short, C.c_short, C.c_short, - C.c_short] -clibmseed.ms_readtraces_timewin.restype = C.c_int - -clibmseed.msr_starttime.argtypes = [C.POINTER(MSRecord)] -clibmseed.msr_starttime.restype = C.c_int64 - -clibmseed.msr_endtime.argtypes = [C.POINTER(MSRecord)] -clibmseed.msr_endtime.restype = C.c_int64 - -clibmseed.ms_detect.argtypes = [ - np.ctypeslib.ndpointer(dtype=np.int8, ndim=1, - flags=native_str('C_CONTIGUOUS')), - C.c_int] -clibmseed.ms_detect.restype = C.c_int - -clibmseed.msr_decode_steim2.argtypes = [ - C.c_void_p, - C.c_int, - C.c_int, - np.ctypeslib.ndpointer(dtype=np.int32, ndim=1, - flags=native_str('C_CONTIGUOUS')), - C.c_int, C.c_char_p, C.c_int] -clibmseed.msr_decode_steim2.restype = C.c_int - -clibmseed.msr_decode_steim1.argtypes = [ - C.c_void_p, - C.c_int, - C.c_int, - np.ctypeslib.ndpointer(dtype=np.int32, ndim=1, - flags=native_str('C_CONTIGUOUS')), - C.c_int, C.c_char_p, C.c_int] -clibmseed.msr_decode_steim1.restype = C.c_int - -# tricky, C.POINTER(C.c_char) is a pointer to single character fields -# this is completely different to C.c_char_p which is a string -clibmseed.mst_packgroup.argtypes = [ - C.POINTER(MSTraceGroup), C.CFUNCTYPE( - C.c_void_p, C.POINTER(C.c_char), C.c_int, C.c_void_p), - C.c_void_p, C.c_int, C.c_short, C.c_short, C.POINTER(C.c_int), C.c_short, - C.c_short, C.POINTER(MSRecord)] -clibmseed.mst_packgroup.restype = C.c_int - -clibmseed.msr_addblockette.argtypes = [C.POINTER(MSRecord), - C.POINTER(C.c_char), - C.c_int, C.c_int, C.c_int] -clibmseed.msr_addblockette.restype = C.POINTER(BlktLink) - -clibmseed.msr_parse.argtypes = [ - np.ctypeslib.ndpointer(dtype=np.int8, ndim=1), C.c_int, - C.POINTER(C.POINTER(MSRecord)), - C.c_int, C.c_int, C.c_int] -clibmseed.msr_parse.restype = C.c_int - ##################################### # Define the C structures. ##################################### @@ -581,11 +497,11 @@ class MSTraceSeg(C.Structure): ('samprate', C.c_double), # Nominal sample rate (Hz) ('samplecnt', C.c_int64), # Number of samples in trace coverage ('datasamples', C.c_void_p), # Data samples, 'numsamples' of type - # 'sampletype' + # 'sampletype' ('numsamples', C.c_int64), # Number of data samples in datasamples ('sampletype', C.c_char), # Sample type code: a, i, f, d ('prvtptr', C.c_void_p), # Private pointer for general use, unused - # by libmseed + # by libmseed ('prev', C.POINTER(MSTraceSeg)), # Pointer to previous segment ('next', C.POINTER(MSTraceSeg)) # Pointer to next segment ] @@ -603,12 +519,12 @@ class MSTraceID(C.Structure): ('channel', C.c_char * 11), # Channel designation, NULL terminated ('dataquality', C.c_char), # Data quality indicator ('srcname', C.c_char * 45), # Source name (Net_Sta_Loc_Chan_Qual), - # NULL terminated + # NULL terminated ('type', C.c_char), # Trace type code ('earliest', C.c_longlong), # Time of earliest sample ('latest', C.c_longlong), # Time of latest sample ('prvtptr', C.c_void_p), # Private pointer for general use, unused - # by libmseed + # by libmseed ('numsegments', C.c_int), # Number of segments for this ID ('first', C.POINTER(MSTraceSeg)), # Pointer to first of list of segments @@ -648,7 +564,7 @@ class Selections(C.Structure): Selections._fields_ = [ ('srcname', C.c_char * 100), # Matching (globbing) source name: - # Net_Sta_Loc_Chan_Qual + # Net_Sta_Loc_Chan_Qual ('timewindows', C.POINTER(SelectTime)), ('next', C.POINTER(Selections)) ] @@ -669,7 +585,7 @@ class ContinuousSegment(C.Structure): ('timing_quality', C.c_uint8), ('calibration_type', C.c_int8), ('datasamples', C.c_void_p), # Data samples, 'numsamples' of type - # 'sampletype' + # 'sampletype' ('firstRecord', C.c_void_p), ('lastRecord', C.c_void_p), ('next', C.POINTER(ContinuousSegment)), @@ -699,12 +615,98 @@ class LinkedIDList(C.Structure): ] -######################################### -# Done with the C structures definitions. -######################################### +########################################################################## +# Define the argument and return types of all the used libmseed functions. +########################################################################## + +__clibmseed = _load_cdll("mseed") + +# Declare function of libmseed library, argument parsing +__clibmseed.mst_init.argtypes = [C.POINTER(MSTrace)] +__clibmseed.mst_init.restype = C.POINTER(MSTrace) + +__clibmseed.mst_free.argtypes = [C.POINTER(C.POINTER(MSTrace))] +__clibmseed.mst_free.restype = C.c_void_p + +__clibmseed.mst_initgroup.argtypes = [C.POINTER(MSTraceGroup)] +__clibmseed.mst_initgroup.restype = C.POINTER(MSTraceGroup) + +__clibmseed.mst_freegroup.argtypes = [C.POINTER(C.POINTER(MSTraceGroup))] +__clibmseed.mst_freegroup.restype = C.c_void_p + +__clibmseed.msr_init.argtypes = [C.POINTER(MSRecord)] +__clibmseed.msr_init.restype = C.POINTER(MSRecord) + +__clibmseed.ms_readmsr_r.argtypes = [ + C.POINTER(C.POINTER(MSFileParam)), C.POINTER(C.POINTER(MSRecord)), + C.c_char_p, C.c_int, C.POINTER(Py_ssize_t), C.POINTER(C.c_int), C.c_short, + C.c_short, C.c_short] +__clibmseed.ms_readmsr_r.restypes = C.c_int + +__clibmseed.ms_readtraces.argtypes = [ + C.POINTER(C.POINTER(MSTraceGroup)), C.c_char_p, C.c_int, C.c_double, + C.c_double, C.c_short, C.c_short, C.c_short, C.c_short] +__clibmseed.ms_readtraces.restype = C.c_int + +__clibmseed.ms_readtraces_timewin.argtypes = [ + C.POINTER(C.POINTER(MSTraceGroup)), C.c_char_p, C.c_int, C.c_double, + C.c_double, C.c_int64, C.c_int64, C.c_short, C.c_short, C.c_short, + C.c_short] +__clibmseed.ms_readtraces_timewin.restype = C.c_int + +__clibmseed.msr_starttime.argtypes = [C.POINTER(MSRecord)] +__clibmseed.msr_starttime.restype = C.c_int64 + +__clibmseed.msr_endtime.argtypes = [C.POINTER(MSRecord)] +__clibmseed.msr_endtime.restype = C.c_int64 + +__clibmseed.ms_detect.argtypes = [ + np.ctypeslib.ndpointer(dtype=np.int8, ndim=1, + flags=native_str('C_CONTIGUOUS')), + C.c_int] +__clibmseed.ms_detect.restype = C.c_int + +__clibmseed.msr_decode_steim2.argtypes = [ + C.c_void_p, + C.c_int, + C.c_int, + np.ctypeslib.ndpointer(dtype=np.int32, ndim=1, + flags=native_str('C_CONTIGUOUS')), + C.c_int, C.c_char_p, C.c_int] +__clibmseed.msr_decode_steim2.restype = C.c_int + +__clibmseed.msr_decode_steim1.argtypes = [ + C.c_void_p, + C.c_int, + C.c_int, + np.ctypeslib.ndpointer(dtype=np.int32, ndim=1, + flags=native_str('C_CONTIGUOUS')), + C.c_int, C.c_char_p, C.c_int] +__clibmseed.msr_decode_steim1.restype = C.c_int + +# tricky, C.POINTER(C.c_char) is a pointer to single character fields +# this is completely different to C.c_char_p which is a string +__clibmseed.mst_packgroup.argtypes = [ + C.POINTER(MSTraceGroup), C.CFUNCTYPE( + C.c_void_p, C.POINTER(C.c_char), C.c_int, C.c_void_p), + C.c_void_p, C.c_int, C.c_short, C.c_short, C.POINTER(C.c_int), C.c_short, + C.c_short, C.POINTER(MSRecord)] +__clibmseed.mst_packgroup.restype = C.c_int + +__clibmseed.msr_addblockette.argtypes = [C.POINTER(MSRecord), + C.POINTER(C.c_char), + C.c_int, C.c_int, C.c_int] +__clibmseed.msr_addblockette.restype = C.POINTER(BlktLink) + +__clibmseed.msr_parse.argtypes = [ + np.ctypeslib.ndpointer(dtype=np.int8, ndim=1), C.c_int, + C.POINTER(C.POINTER(MSRecord)), + C.c_int, C.c_int, C.c_int] +__clibmseed.msr_parse.restype = C.c_int + # Set the necessary arg- and restypes. -clibmseed.readMSEEDBuffer.argtypes = [ +__clibmseed.readMSEEDBuffer.argtypes = [ np.ctypeslib.ndpointer(dtype=np.int8, ndim=1, flags=native_str('C_CONTIGUOUS')), C.c_int, @@ -714,46 +716,103 @@ class LinkedIDList(C.Structure): C.c_int8, C.c_int8, C.c_int, - C.CFUNCTYPE(C.c_longlong, C.c_int, C.c_char), - C.CFUNCTYPE(C.c_void_p, C.c_char_p), - C.CFUNCTYPE(C.c_void_p, C.c_char_p) + C.CFUNCTYPE(C.c_longlong, C.c_int, C.c_char) ] +__clibmseed.readMSEEDBuffer.restype = C.POINTER(LinkedIDList) -clibmseed.readMSEEDBuffer.restype = C.POINTER(LinkedIDList) -clibmseed.msr_free.argtypes = [C.POINTER(C.POINTER(MSRecord))] -clibmseed.msr_free.restype = C.c_void_p +__clibmseed.setupLogging.argtpyes = [ + C.c_int8, + C.CFUNCTYPE(C.c_void_p, C.c_char_p), + C.CFUNCTYPE(C.c_void_p, C.c_char_p)] +__clibmseed.setupLogging.restype = C.c_void_p -clibmseed.mstl_init.restype = C.POINTER(MSTraceList) -clibmseed.mstl_free.argtypes = [C.POINTER(C.POINTER(MSTraceList)), C.c_int] +__clibmseed.msr_free.argtypes = [C.POINTER(C.POINTER(MSRecord))] +__clibmseed.msr_free.restype = C.c_void_p -clibmseed.lil_free.argtypes = [C.POINTER(LinkedIDList)] -clibmseed.lil_free.restype = C.c_void_p +__clibmseed.mstl_init.restype = C.POINTER(MSTraceList) +__clibmseed.mstl_free.argtypes = [C.POINTER(C.POINTER(MSTraceList)), C.c_int] -clibmseed.allocate_bytes.argtypes = (C.c_int,) -clibmseed.allocate_bytes.restype = C.c_void_p +__clibmseed.lil_free.argtypes = [C.POINTER(LinkedIDList)] +__clibmseed.lil_free.restype = C.c_void_p -clibmseed.ms_genfactmult.argtypes = [ + +__clibmseed.allocate_bytes.argtypes = (C.c_int,) +__clibmseed.allocate_bytes.restype = C.c_void_p + + +__clibmseed.ms_genfactmult.argtypes = [ C.c_double, C.POINTER(C.c_int16), C.POINTER(C.c_int16) ] -clibmseed.ms_genfactmult.restype = C.c_int +__clibmseed.ms_genfactmult.restype = C.c_int -clibmseed.ms_nomsamprate.argtypes = [ +__clibmseed.ms_nomsamprate.argtypes = [ C.c_int, C.c_int ] -clibmseed.ms_nomsamprate.restype = C.c_double +__clibmseed.ms_nomsamprate.restype = C.c_double -# Python callback functions for C -def _py_file_callback(_f): - return 1 - +class _LibmseedWrapper(object): + """ + Wrapper object around libmseed that tries to guarantee that all warnings + and errors within libmseed are properly converted to their Python + counterparts. -_PyFile_callback = C.CFUNCTYPE(C.c_int, Py_ssize_t)(_py_file_callback) + Might be a bit overengineered but it does the trick and is completely + transparent to the user. + """ + def __init__(self, lib): + self.lib = lib + self.verbose = True + + def __getattr__(self, item): + func = getattr(self.lib, item) + + def _wrapper(*args): + # Collect exceptions. They cannot be raised in the callback as + # they could never be caught then. They are collected an raised + # later on. + _errs = [] + _warns = [] + + def log_error_or_warning(msg): + msg = msg.decode() + if msg.startswith("ERROR: "): + msg = msg[7:].strip() + _errs.append(msg) + if msg.startswith("INFO: "): + msg = msg[6:].strip() + _warns.append(msg) + + diag_print = \ + C.CFUNCTYPE(C.c_void_p, C.c_char_p)(log_error_or_warning) + + def log_message(msg): + if self.verbose: + print(msg[6:].strip()) + log_print = C.CFUNCTYPE(C.c_void_p, C.c_char_p)(log_message) + + # Hookup libmseed's logging facilities to it's Python callbacks. + self.lib.setupLogging(diag_print, log_print) + + try: + return func(*args) + finally: + for _w in _warns: + warnings.warn(_w, InternalMSEEDWarning) + if _errs: + msg = ("Encountered %i error(s) during a call to " + "%s():\n%s" % ( + len(_errs), item, "\n".join(_errs))) + raise InternalMSEEDError(msg) + return _wrapper + + +clibmseed = _LibmseedWrapper(lib=__clibmseed) diff --git a/obspy/io/mseed/src/obspy-readbuffer.c b/obspy/io/mseed/src/obspy-readbuffer.c index 3c794daaec9..b3afa835313 100644 --- a/obspy/io/mseed/src/obspy-readbuffer.c +++ b/obspy/io/mseed/src/obspy-readbuffer.c @@ -260,13 +260,20 @@ void log_error(int errcode, int offset) { } +// Helper function to connect libmseed's logging and error messaging to Python +// functions. +void setupLogging(void (*diag_print) (char*), + void (*log_print) (char*)) { + ms_loginit(log_print, "INFO: ", diag_print, "ERROR: "); +} + + // Function that reads from a MiniSEED binary file from a char buffer and // returns a LinkedIDList. LinkedIDList * readMSEEDBuffer (char *mseed, int buflen, Selections *selections, flag unpack_data, int reclen, flag verbose, flag details, - int header_byteorder, long long (*allocData) (int, char), - void (*diag_print) (char*), void (*log_print) (char*)) + int header_byteorder, long long (*allocData) (int, char)) { int retcode = 0; int retval = 0; @@ -301,14 +308,6 @@ readMSEEDBuffer (char *mseed, int buflen, Selections *selections, flag int datasize; int record_count = 0; - // A negative verbosity suppresses as much as possible. - if (verbose < 0) { - ms_loginit(&empty_print, NULL, &empty_print, NULL); - } - else { - ms_loginit(log_print, "INFO: ", diag_print, "ERROR: "); - } - if (header_byteorder >= 0) { // Enforce little endian. if (header_byteorder == 0) { diff --git a/obspy/io/mseed/src/obspy-readbuffer.def b/obspy/io/mseed/src/obspy-readbuffer.def index 2783d3af263..25142bad49a 100644 --- a/obspy/io/mseed/src/obspy-readbuffer.def +++ b/obspy/io/mseed/src/obspy-readbuffer.def @@ -1,6 +1,7 @@ LIBRARY libmseed.dll EXPORTS readMSEEDBuffer + setupLogging lil_init lrl_free seg_free diff --git a/obspy/io/mseed/tests/data/wrong_blockette_numbers_specified.mseed b/obspy/io/mseed/tests/data/wrong_blockette_numbers_specified.mseed new file mode 100644 index 00000000000..888168baa72 Binary files /dev/null and b/obspy/io/mseed/tests/data/wrong_blockette_numbers_specified.mseed differ diff --git a/obspy/io/mseed/tests/test_mseed_reading_and_writing.py b/obspy/io/mseed/tests/test_mseed_reading_and_writing.py index 9440be89c5f..64831d603d0 100644 --- a/obspy/io/mseed/tests/test_mseed_reading_and_writing.py +++ b/obspy/io/mseed/tests/test_mseed_reading_and_writing.py @@ -19,8 +19,8 @@ from obspy import Stream, Trace, UTCDateTime, read from obspy.core import AttribDict from obspy.core.util import CatchOutput, NamedTemporaryFile -from obspy.io.mseed import (util, InternalMSEEDReadingWarning, - InternalMSEEDReadingError) +from obspy.io.mseed import (util, InternalMSEEDWarning, + InternalMSEEDError) from obspy.io.mseed.core import _is_mseed, _read_mseed, _write_mseed from obspy.io.mseed.headers import ENCODINGS, clibmseed from obspy.io.mseed.msstruct import _MSStruct @@ -1146,7 +1146,7 @@ def test_verbosity(self): st = read(filename, verbose=2) self.assertEqual(len(w), 1) - self.assertEqual(w[0].category, InternalMSEEDReadingWarning) + self.assertEqual(w[0].category, InternalMSEEDWarning) self.assertIn(b"calling msr_parse with", out.stdout) self.assertIn(b"buflen=512, reclen=-1, dataflag=0, verbose=2", @@ -1407,7 +1407,7 @@ def assert_valid(filename, reference, test_type): # There is only one file that uses this so far so special # handling is okay I guess. self.assertIn("invalid-blockette-offset", filename) - with self.assertRaises(InternalMSEEDReadingError, + with self.assertRaises(InternalMSEEDError, msg=filename) as e: # The file has a couple other issues as well and the # data cannot be unpacked. Unpacking it would raises an @@ -1417,7 +1417,7 @@ def assert_valid(filename, reference, test_type): with io.open(reference, "rt") as fh: err_msg = fh.readlines()[-1] err_msg = re.sub("^Error:\s", "", err_msg).strip() - self.assertEqual(err_msg, e.exception.args[0]) + self.assertEqual(err_msg, e.exception.args[0].splitlines()[1]) elif test_type == "summary": st = read(filename) # This is mainly used for a test with chunks in arbitrary diff --git a/obspy/io/mseed/tests/test_mseed_special_issues.py b/obspy/io/mseed/tests/test_mseed_special_issues.py index d86f7046a75..c9f15b4c15c 100644 --- a/obspy/io/mseed/tests/test_mseed_special_issues.py +++ b/obspy/io/mseed/tests/test_mseed_special_issues.py @@ -21,8 +21,8 @@ from obspy.core.compatibility import from_buffer from obspy.core.util import NamedTemporaryFile from obspy.core.util.attribdict import AttribDict -from obspy.io.mseed import InternalMSEEDReadingError, \ - InternalMSEEDReadingWarning +from obspy.io.mseed import InternalMSEEDError, \ + InternalMSEEDWarning from obspy.io.mseed import util from obspy.io.mseed.core import _read_mseed, _write_mseed from obspy.io.mseed.headers import clibmseed @@ -45,7 +45,7 @@ def _test_function(filename): warnings.simplefilter("always") try: st = read(filename) # noqa @UnusedVariable - except (ValueError, InternalMSEEDReadingError): + except (ValueError, InternalMSEEDError): # Should occur with broken files pass @@ -143,7 +143,7 @@ def test_broken_last_record(self): data_record = _read_mseed(file)[0].data self.assertEqual(len(w), 1) - self.assertEqual(w[0].category, InternalMSEEDReadingWarning) + self.assertEqual(w[0].category, InternalMSEEDWarning) # Test against reference data. self.assertEqual(len(data_record), 5980) @@ -287,7 +287,7 @@ def test_wrong_record_length_as_argument(self): # wrong reclen - raises and also displays a warning self.assertRaises(Exception, read, file, reclen=4096) self.assertTrue('reclen exceeds buflen' in str(w[1].message)) - self.assertEqual(w[1].category, InternalMSEEDReadingWarning) + self.assertEqual(w[1].category, InternalMSEEDWarning) def test_read_with_missing_blockette010(self): """ diff --git a/obspy/io/mseed/tests/test_mseed_util.py b/obspy/io/mseed/tests/test_mseed_util.py index c054a128773..f438ef16553 100644 --- a/obspy/io/mseed/tests/test_mseed_util.py +++ b/obspy/io/mseed/tests/test_mseed_util.py @@ -11,9 +11,9 @@ import shutil import sys import unittest -import warnings from datetime import datetime from struct import pack, unpack +import warnings import numpy as np @@ -1136,6 +1136,32 @@ def test_set_flags_in_fixed_header_with_blockette_100(self): self.assertEqual(flags['glitches'], 2) self.assertEqual(flags['suspect_time_tag'], 2) + def test_regression_segfault_when_hooking_up_libmseeds_logging(self): + filename = os.path.join(self.path, 'data', + 'wrong_blockette_numbers_specified.mseed') + # Read it once - that hooks up the logging. + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + _read_mseed(filename) + self.assertTrue(len(w), 1) + self.assertEqual( + w[0].message.args[0], + "SK_MODS__HHZ_D: Warning: Number of blockettes in fixed header " + "(2) does not match the number parsed (1)") + # The hooks used to still be set up in libmseed but the + # corresponding Python function have been garbage collected which + # caused a segfault. + # + # This test passes if there is no seg-fault. + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + util.get_flags(filename) + self.assertTrue(len(w), 1) + self.assertEqual( + w[0].message.args[0], + "SK_MODS__HHZ_D: Warning: Number of blockettes in fixed header " + "(2) does not match the number parsed (1)") + def _check_values(self, file_bfr, trace_id, record_numbers, expected_bytes, reclen): """