Skip to content
Snippets Groups Projects
Commit efffa7a2 authored by Rosen Matev's avatar Rosen Matev :sunny:
Browse files

Merge branch 'rm-error-stream' into '2024-patches'

Error event handling

See merge request !3883
parents f1b5f0c3 2813f008
No related branches found
No related tags found
4 merge requests!4553Fix UT Decoder,!4539Synchronize master branch with 2024-patches,!4525Draft: Synchronize master branch with 2024-patches,!3883Error event handling
Pipeline #7250010 passed
Showing
with 522 additions and 11 deletions
......@@ -8,14 +8,15 @@
* granted to it by virtue of its status as an Intergovernmental Organization *
* or submit itself to any jurisdiction. *
\*****************************************************************************/
#include <memory>
#include "Gaudi/Algorithm.h"
#include "GaudiAlg/FunctionalDetails.h"
#include "GaudiKernel/DataObjectHandle.h"
#include "GaudiKernel/FunctionalFilterDecision.h"
#include "GaudiKernel/RegistryEntry.h"
#include <csignal>
#include <memory>
class ConfigurableDummy : public Gaudi::Algorithm {
using Algorithm::Algorithm;
......@@ -26,8 +27,11 @@ public:
StatusCode initialize() override;
private:
/// Pick up late-attributed data outputs
Gaudi::Property<int> m_CFD{this, "CFD", 1, "ControlFlowDecision is true every Nth events"};
Gaudi::Property<int> m_CFD{this, "CFD", 1, "ControlFlowDecision is true every Nth events"};
Gaudi::Property<std::vector<bool>> m_CFDs{this, "CFDs", {}, "Vector of decisions, overrides CFD"};
Gaudi::Property<bool> m_decisionWarning{this, "DecisionWarning", false, "Emit a warning for false decisions"};
Gaudi::Property<bool> m_decisionException{this, "DecisionException", false, "Throw an exception for false decisions"};
Gaudi::Property<int> m_signal{this, "Signal", 0, "Raise signal"};
Gaudi::Property<std::vector<std::string>> m_inpKeys{this, "inpKeys", {}, ""};
Gaudi::Property<std::vector<std::string>> m_outKeys{this, "outKeys", {}, ""};
......@@ -93,6 +97,17 @@ StatusCode ConfigurableDummy::execute( EventContext const& context ) const // th
outputHandle->put( std::make_unique<DataObject>() );
}
if ( m_CFD > 0 && context.evt() % m_CFD == 0 ) return Gaudi::Functional::FilterDecision::PASSED;
return Gaudi::Functional::FilterDecision::FAILED;
bool decision = false;
if ( !m_CFDs.empty() ) {
decision = m_CFDs[context.evt() % m_CFDs.size()];
} else {
decision = m_CFD > 0 && context.evt() % m_CFD == 0;
}
if ( m_decisionWarning && !decision ) { warning() << "Event did not pass" << endmsg; }
if ( m_decisionException && !decision ) {
throw GaudiException( "Event did not pass", __PRETTY_FUNCTION__, StatusCode::FAILURE );
}
if ( m_signal > 0 ) { std::raise( m_signal ); }
return decision ? Gaudi::Functional::FilterDecision::PASSED : Gaudi::Functional::FilterDecision::FAILED;
}
......@@ -170,7 +170,7 @@ private:
Default choice is deduced from #slots and #evts \
to be reasonably far away from the end of processing"};
Gaudi::Property<int> m_stopAfterNFailures{this, "StopAfterNFailures", 3,
Gaudi::Property<int> m_stopAfterNFailures{this, "StopAfterNFailures", 1,
"Stop processing if this number of consecutive event failures happened"};
Gaudi::Property<std::size_t> m_minNameColWidth{this, "MinNameColumnWidth", 46u,
......@@ -211,6 +211,7 @@ private:
std::unique_ptr<tbb::task_arena> m_taskArena;
mutable Gaudi::Accumulators::Counter<> m_finishedEvt{this, "Processed events"};
mutable Gaudi::Accumulators::Counter<> m_failedEvt{this, "Failed events"};
/// condition variable to wake up main thread when we need to create a new event
mutable std::condition_variable m_createEventCond;
/// mutex assoiciated with m_createEventCond condition variable
......@@ -694,6 +695,10 @@ void HLTControlFlowMgr::push( EventContext&& evtContext ) {
std::vector{m_NodeStates.begin(), m_NodeStates.end(), LHCb::Allocators::EventLocal<NodeState>{memResource}},
std::vector{m_AlgStates.begin(), m_AlgStates.end(), LHCb::Allocators::EventLocal<AlgState>{memResource}} );
// Reset the status for the current context. If not done, marking it as okay
// with updateEventStatus(false, ctx) has no effect.
m_algExecStateSvc->reset( evtContext );
bool eventFailed = false;
for ( AlgWrapper& toBeRun : m_definitelyRunTheseAlgs ) {
try {
......@@ -702,7 +707,7 @@ void HLTControlFlowMgr::push( EventContext&& evtContext ) {
m_TimingCounters[toBeRun.m_executedIndex] += LHCb::chrono::fast_clock::now() - start;
} catch ( ... ) {
m_algExecStateSvc->updateEventStatus( true, evtContext );
fatal() << "ERROR: Event failed in Algorithm " << toBeRun.name() << endmsg;
fatal() << "Event failed in Algorithm " << toBeRun.name() << endmsg;
if ( !Gaudi::setAppReturnCode( appmgr, Gaudi::ReturnCode::AlgorithmFailure ) )
warning() << "failed to set application return code to " << Gaudi::ReturnCode::AlgorithmFailure << endmsg;
eventFailed = true;
......@@ -856,6 +861,7 @@ StatusCode HLTControlFlowMgr::nextEvent( int maxevt ) {
while ( maxevt < 0 || m_nextevt < maxevt ) {
if ( m_shutdown_now ) {
fatal() << "*** Too many consecutive failures " << m_failed_evts_detected << ", stopping now ***" << endmsg;
shutdown_threadpool();
while ( !empty() ) pop();
return StatusCode::FAILURE;
......@@ -945,7 +951,8 @@ std::optional<IOpaqueAddress*> HLTControlFlowMgr::declareEventRootAddress() {
* them) and events in the queues and returns a failure.
*/
StatusCode HLTControlFlowMgr::eventFailed( EventContext const& eventContext ) const {
fatal() << "*** Event " << eventContext.evt() << " on slot " << eventContext.slot() << " failed! ***" << endmsg;
++m_failedEvt;
fatal() << "Event " << eventContext.evt() << " on slot " << eventContext.slot() << " failed!" << endmsg;
if ( ++m_failed_evts_detected >= m_stopAfterNFailures ) { m_shutdown_now = true; }
return StatusCode::FAILURE;
}
......@@ -978,7 +985,6 @@ void HLTControlFlowMgr::promoteToExecuted( EventContext&& eventContext ) const {
verbose() << "Event " << eventContext.evt() << " finished (slot " << si << ")." << endmsg;
} else {
eventFailed( eventContext ).ignore();
fatal() << "Failed event detected on " << eventContext << endmsg;
}
if constexpr ( LHCb::Allocators::Utils::provides_stats_v<LHCb::Allocators::MonotonicBufferResource> ) {
......
......@@ -40,7 +40,7 @@ app = ApplicationMgr(
EvtMax=100,
EvtSel='NONE',
ExtSvc=[whiteboard, 'Gaudi::Monitoring::MessageSvcSink'],
EventLoop=HLTControlFlowMgr(StopAfterNFailures=1),
EventLoop=HLTControlFlowMgr(),
TopAlg=[producer, transformer, consumer1, consumer2])
HiveDataBrokerSvc().DataProducers = app.TopAlg
###############################################################################
# (c) Copyright 2000-2018 CERN for the benefit of the LHCb Collaboration #
# #
# This software is distributed under the terms of the GNU General Public #
# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". #
# #
# In applying this licence, CERN does not waive the privileges and immunities #
# granted to it by virtue of its status as an Intergovernmental Organization #
# or submit itself to any jurisdiction. #
###############################################################################
from Configurables import (
HLTControlFlowMgr,
HiveWhiteBoard,
HiveDataBrokerSvc,
ConfigurableDummy,
)
from Gaudi.Configuration import ApplicationMgr
isolated_fails = ([True] * 9 + [False] * 1) * 10
algs = [
ConfigurableDummy(
name='IsolatedFails',
CFDs=isolated_fails,
DecisionException=True,
),
]
whiteboard = HiveWhiteBoard("EventDataSvc", EventSlots=1)
HLTControlFlowMgr().CompositeCFNodes = [
('top', 'NONLAZY_OR', [a.name() for a in algs], True),
]
HLTControlFlowMgr().StopAfterNFailures = 2
HLTControlFlowMgr().ThreadPoolSize = 1
app = ApplicationMgr(
EvtMax=100,
EvtSel='NONE',
ExtSvc=[whiteboard, 'Gaudi::Monitoring::MessageSvcSink'],
EventLoop=HLTControlFlowMgr(StopAfterNFailures=3),
TopAlg=algs)
HiveDataBrokerSvc().DataProducers = app.TopAlg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'>
<!--
(c) Copyright 2000-2024 CERN for the benefit of the LHCb Collaboration
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".
In applying this licence, CERN does not waive the privileges and immunities
granted to it by virtue of its status as an Intergovernmental Organization
or submit itself to any jurisdiction.
-->
<extension class="GaudiTest.GaudiExeTest" kind="test">
<argument name="program"><text>gaudirun.py</text></argument>
<argument name="args"><set>
<text>-v</text>
<text>../options/testSkipIsolatedFailures.py</text>
</set></argument>
<argument name="use_temp_dir"><enumeral>per-test</enumeral></argument>
<argument name="exit_code"><integer>3</integer></argument>
<argument name="validator"><text>
from GaudiConf.QMTest.LHCbTest import extract_counters
counters = extract_counters(stdout)
n_processed = int(counters["HLTControlFlowMgr"]["Processed events"][0])
if n_processed != 100:
causes.append("number of processed events")
n_failed = int(counters["HLTControlFlowMgr"]["Failed events"][0])
if n_failed != 10:
causes.append(f"number of failed events")
</text></argument>
</extension>
......@@ -58,6 +58,7 @@ gaudi_add_module(LHCbAlgs
src/ProcessPhase.cpp
src/RunChangeTest.cpp
src/ServiceStarter.cpp
src/SignalMDFWriter.cpp
src/TESCheck.cpp
src/TESFingerPrint.cpp
src/TESMerger.cpp
......@@ -66,6 +67,7 @@ gaudi_add_module(LHCbAlgs
src/TimingTool.cpp
src/TrajPoca.cpp
src/createODIN.cpp
src/MessageSvcEventFilter.cpp
LINK
AIDA::aida
Boost::filesystem
......
/*****************************************************************************\
* (c) Copyright 2022-2024 CERN for the benefit of the LHCb Collaboration *
* *
* This software is distributed under the terms of the GNU General Public *
* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". *
* *
* In applying this licence, CERN does not waive the privileges and immunities *
* granted to it by virtue of its status as an Intergovernmental Organization *
* or submit itself to any jurisdiction. *
\*****************************************************************************/
#include "LHCbAlgs/FilterPredicate.h"
/** @class MessageSvcEventFilter
*
* Pass when the processing of the event caused a certain message.
*
* This filter can be configured to filter on ERROR/FATAL messages, for example,
* and can be inserted in the control flow before a writer.
* In this way one can create an "error stream" and save events that
* exhibit errors in the processing.
*
* The algorithm hooks into the MessageSvc by modifying the default stream (at start).
* The modified ostream matches the formatted messages it sees with a regex pattern.
* (Take care to synchronise the MessageSvc format with the pattern.)
* If the message matches, a "flag" object is put on the TES (in the slot assigned to
* the algorithm's execution that emitted the message).
* Then, when the MessageSvcEventFilter algorithm is executed, it simply checks the existence of
* the flag object on the TES and passes only if it finds one.
*
* Note on implementation. The interface msgSvc()->insertStream() seems more appropriate but we
* cannot use it. Using it would look like this
* msgSvc()->insertStream( MSG::WARNING, "default_stream", msgSvc()->defaultStream() );
* msgSvc()->insertStream( MSG::WARNING, name() + "_spy_stream", m_spyStream.get() );
* ...
* msgSvc()->eraseStream( MSG::WARNING );
* The problem is that the MessageSvc as implemented does not apply formatting in the case
* of explicitly inserted streams. It is impossible to apply it here (we only see strings).
* TODO report/fix this in Gaudi
*
*/
namespace {
using DataHandle = DataObjectWriteHandle<DataObject>;
std::ostream* s_origStream = nullptr;
class spystreambuf : public std::stringbuf {
public:
explicit spystreambuf( std::string pattern, DataHandle& dh ) : m_pattern( pattern ), m_dataHandle( dh ) {}
private:
int sync() override {
std::ptrdiff_t n = pptr() - pbase();
std::string temp;
temp.assign( pbase(), n );
*s_origStream << temp << std::flush;
pbump( -n );
return 0;
}
std::streamsize xsputn( const char_type* s, std::streamsize count ) override {
std::string temp;
temp.assign( s, count );
*s_origStream << temp;
// FIXME actually use pattern here
if ( m_dataHandle.getIfExists() == nullptr ) {
try {
m_dataHandle.put( std::make_unique<DataObject>() );
} catch ( GaudiException& e ) { std::cerr << e.message() << std::endl; };
}
return count;
}
int_type overflow( int_type ch ) override {
*s_origStream << (char)ch;
return std::stringbuf::overflow( ch );
}
// spystreambuf( const spystreambuf& );
// spystreambuf& operator=( const spystreambuf& );
std::string m_pattern;
DataHandle& m_dataHandle;
};
} // namespace
class MessageSvcEventFilter : public LHCb::Algorithm::FilterPredicate<bool()> {
public:
using FilterPredicate::FilterPredicate;
StatusCode start() override;
StatusCode stop() override;
bool operator()() const override;
private:
Gaudi::Property<std::string> m_pattern{
this,
"PatternRegex",
"",
[this]( const auto& ) { this->m_regex = this->m_pattern.value(); },
"std::regex pattern that matches messages",
};
DataHandle m_flag{this, "FlagLocation", ""};
mutable Gaudi::Accumulators::BinomialCounter<> m_counter{this, "Accepted"};
std::regex m_regex;
std::unique_ptr<spystreambuf> m_spyBuf;
std::unique_ptr<std::ostream> m_spyStream;
};
DECLARE_COMPONENT( MessageSvcEventFilter )
StatusCode MessageSvcEventFilter::start() {
return FilterPredicate::start().andThen( [&] {
if ( !s_origStream ) {
s_origStream = msgSvc()->defaultStream();
// TODO treat missing (nullptr) default stream?
m_spyStream.reset(); // in case we're called a second time, destroy the stream before the buffer
m_spyBuf = std::make_unique<spystreambuf>( "pattern", m_flag );
m_spyStream = std::make_unique<std::ostream>( m_spyBuf.get() );
msgSvc()->setDefaultStream( m_spyStream.get() );
}
} );
}
StatusCode MessageSvcEventFilter::stop() {
if ( s_origStream ) {
msgSvc()->setDefaultStream( s_origStream );
s_origStream = nullptr;
}
return FilterPredicate::stop();
}
bool MessageSvcEventFilter::operator()() const {
bool pass = m_flag.getIfExists() != nullptr;
m_counter += pass;
return pass;
}
/*****************************************************************************\
* (c) Copyright 2022 CERN for the benefit of the LHCb Collaboration *
* *
* This software is distributed under the terms of the GNU General Public *
* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". *
* *
* In applying this licence, CERN does not waive the privileges and immunities *
* granted to it by virtue of its status as an Intergovernmental Organization *
* or submit itself to any jurisdiction. *
\*****************************************************************************/
#include "Event/RawEvent.h"
#include "LHCbAlgs/Consumer.h"
#include <csignal>
#include <fcntl.h>
#include <fmt/format.h>
#include <stdio.h>
/** @class SignalMDFWriter
*
* PrepareMDFSignalHandler
*
* Register signal handler that writes the raw event.
*
* The behavior is undefined if any signal handler performs any of the following:
* ...
* - access to an object with thread storage duration
*
* https://maskray.me/blog/2021-02-14-all-about-thread-local-storage
*
*/
namespace {
using DataHandle = DataObjectReadHandle<LHCb::RawEvent>;
constexpr size_t OUTPUT_PATH_SIZE = 1024;
DataHandle* s_rawEventHandle = nullptr;
char s_outputPath[OUTPUT_PATH_SIZE] = "";
void signal_handler( int ) {
auto f = ::open( s_outputPath, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH );
if ( f == -1 ) { std::_Exit( EXIT_FAILURE ); }
const auto* rawEvent = s_rawEventHandle->getIfExists();
if ( rawEvent ) {
// MDF event header
LHCb::RawBank const* const daqBank = rawEvent->banks( LHCb::RawBank::DAQ )[0];
::write( f, daqBank->data(), daqBank->totalSize() - daqBank->hdrSize() );
// MDF raw banks
constexpr auto types = LHCb::RawBank::types();
for ( const auto type : types ) {
if ( type != LHCb::RawBank::DAQ ) {
for ( LHCb::RawBank const* const bank : rawEvent->banks( type ) ) { ::write( f, bank, bank->totalSize() ); }
}
}
}
::close( f );
std::_Exit( EXIT_FAILURE );
}
} // namespace
class SignalMDFWriter : public LHCb::Algorithm::Consumer<void()> {
public:
SignalMDFWriter( const std::string& name, ISvcLocator* pSvcLocator ) : Consumer( name, pSvcLocator ) {}
StatusCode start() override;
StatusCode stop() override;
void operator()() const override;
private:
Gaudi::Property<std::string> m_outputPrefix{this, "OutputPrefix", "signal"};
Gaudi::Property<std::vector<int>> m_signals{this, "Signals", {SIGILL, SIGABRT, SIGBUS, SIGSEGV}};
DataHandle m_rawEvent{this, "RawEvent", ""};
};
DECLARE_COMPONENT( SignalMDFWriter )
StatusCode SignalMDFWriter::start() {
return Consumer::start().andThen( [&] {
s_rawEventHandle = &m_rawEvent;
for ( auto signal : m_signals ) std::signal( signal, signal_handler );
} );
}
StatusCode SignalMDFWriter::stop() { return Consumer::stop(); }
void SignalMDFWriter::operator()() const {
// 1. Make sure the transient MapView is allocated and initialized.
// 2. Obtain the ODIN raw data to construct the name of the output.
auto const* rawEvent = m_rawEvent.get();
auto const banks = rawEvent->banks( LHCb::RawBank::ODIN );
if ( banks.size() != 1 ) {
throw GaudiException( "Unexpected number of ODIN banks: " + std::to_string( banks.size() ), __PRETTY_FUNCTION__,
StatusCode::FAILURE );
}
// Assume ODIN v7
auto s = fmt::format( "{}_{:010}_{:020}.mdf", m_outputPrefix.value(), banks[0]->data()[0],
static_cast<uint64_t>( banks[0]->data()[8] ) +
( static_cast<uint64_t>( banks[0]->data()[9] ) << 32 ) );
if ( s.size() + 1 > OUTPUT_PATH_SIZE ) {
throw GaudiException( "OutputPrefix property is too long", __PRETTY_FUNCTION__, StatusCode::FAILURE );
}
std::strcpy( s_outputPath, s.c_str() );
}
###############################################################################
# (c) Copyright 2022-2024 CERN for the benefit of the LHCb Collaboration #
# #
# This software is distributed under the terms of the GNU General Public #
# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". #
# #
# In applying this licence, CERN does not waive the privileges and immunities #
# granted to it by virtue of its status as an Intergovernmental Organization #
# or submit itself to any jurisdiction. #
###############################################################################
from PyConf.Algorithms import (
MessageSvcEventFilter,
ConfigurableDummy,
)
from PyConf.application import ApplicationOptions, configure, configure_input
from PyConf.control_flow import CompositeNode, NodeLogic
message_producer = ConfigurableDummy(
CFD=3, DecisionWarning=True) # 33% prescale
algs_node = CompositeNode(
"algs", [message_producer],
combine_logic=NodeLogic.LAZY_AND,
force_order=True)
event_filter = MessageSvcEventFilter()
writer_node = CompositeNode(
"writer", [event_filter],
combine_logic=NodeLogic.LAZY_AND,
force_order=True)
top = CompositeNode(
"top", [algs_node, writer_node],
combine_logic=NodeLogic.NONLAZY_AND,
force_order=True)
# Define the application environment and run it
options = ApplicationOptions(_enabled=False)
options.n_threads = 4
options.n_event_slots = 4
options.evt_max = 50
options.input_type = "NONE"
options.dddb_tag = "dummy"
options.conddb_tag = "dummy"
options.simulation = False
config2 = configure_input(options)
config = configure(options, top)
###############################################################################
# (c) Copyright 2022 CERN for the benefit of the LHCb Collaboration #
# #
# This software is distributed under the terms of the GNU General Public #
# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". #
# #
# In applying this licence, CERN does not waive the privileges and immunities #
# granted to it by virtue of its status as an Intergovernmental Organization #
# or submit itself to any jurisdiction. #
###############################################################################
from PyConf.Algorithms import (
SignalMDFWriter,
ConfigurableDummy,
)
from PyConf.application import (
ApplicationOptions,
configure,
configure_input,
default_raw_event,
)
from PyConf.control_flow import CompositeNode, NodeLogic
options = ApplicationOptions(_enabled=False)
options.set_input_and_conds_from_testfiledb('2023_raw_hlt1_269939')
options.n_threads = 1
options.n_event_slots = 4
options.evt_max = 50
# options.use_iosvc = True
options.event_store = 'EvtStoreSvc'
options.scheduler_legacy_mode = False
configure_input(options)
ensure_event = SignalMDFWriter(
OutputPrefix="signal", RawEvent=default_raw_event(["VP"]))
prescaler = ConfigurableDummy(CFD=20)
crash = ConfigurableDummy(Signal=11)
top = CompositeNode(
"top", [ensure_event, prescaler, crash],
combine_logic=NodeLogic.LAZY_AND,
force_order=True)
configure(options, top)
from Configurables import LHCb__DetDesc__ReserveDetDescForEvent as reserveIOV
reserveIOV("reserveIOV").PreloadGeometry = False
<?xml version="1.0" ?><!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'>
<!--
(c) Copyright 2000-2024 CERN for the benefit of the LHCb Collaboration
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".
In applying this licence, CERN does not waive the privileges and immunities
granted to it by virtue of its status as an Intergovernmental Organization
or submit itself to any jurisdiction.
-->
<extension class="GaudiTest.GaudiExeTest" kind="test">
<argument name="program"><text>gaudirun.py</text></argument>
<argument name="args"><set>
<text>../options/error_stream.py</text>
</set></argument>
<argument name="use_temp_dir"><enumeral>per-test</enumeral></argument>
<argument name="validator">
<text>
countErrorLines({"FATAL": 0, "ERROR": 0, "WARNING": 33})
# Being determinisitic, the emulator should always accept the same events and
# hence emulate the same accept fractions
findReferenceBlock("""
NONLAZY_AND: top #=50 Sum=0 Eff=|( 0.000000 +- 0.00000 )%|
LAZY_AND: algs #=50 Sum=17 Eff=|( 34.00000 +- 6.69925 )%|
ConfigurableDummy/ConfigurableDummy_b0635f1e #=50 Sum=17 Eff=|( 34.00000 +- 6.69925 )%|
LAZY_AND: writer #=50 Sum=33 Eff=|( 66.00000 +- 6.69925 )%|
MessageSvcEventFilter/MessageSvcEventFilter_56e8af36 #=50 Sum=33 Eff=|( 66.00000 +- 6.69925 )%|
""")
</text>
</argument>
</extension>
<?xml version="1.0" ?><!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'>
<!--
(c) Copyright 2000-2024 CERN for the benefit of the LHCb Collaboration
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".
In applying this licence, CERN does not waive the privileges and immunities
granted to it by virtue of its status as an Intergovernmental Organization
or submit itself to any jurisdiction.
-->
<extension class="GaudiTest.GaudiExeTest" kind="test">
<argument name="program"><text>gaudirun.py</text></argument>
<argument name="args"><set>
<text>../options/signal_writer.py</text>
</set></argument>
<argument name="use_temp_dir"><enumeral>per-test</enumeral></argument>
<argument name="exit_code"><integer>1</integer></argument>
<argument name="validator">
<text>
countErrorLines({"FATAL": 0, "ERROR": 0, "WARNING": 0})
import os
expected_fn = "signal_0000269939_00000000000001450050.mdf"
expected_size = 28032
try:
size = os.path.getsize(expected_fn)
if size != expected_size:
causes.append(f"size {size} != expected {expected_size}")
except Exception as e:
causes.append(str(e))
</text>
</argument>
</extension>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment