Refactoring of the interpreter scheme with the goal to provide a functional cleanup. This is required if someone stops inside the debugger. Without a proper cleanup the application becomes dysfunctional.

This commit is contained in:
Matthias Koefferlein 2021-03-15 10:17:25 +01:00
parent f2b1661647
commit a8f3df7aa5
11 changed files with 444 additions and 133 deletions

View File

@ -1859,6 +1859,7 @@ CODE
@in_context = func
return yield(*args)
rescue => ex
RBA::MacroExecutionContext::ignore_next_exception
raise("'" + func + "': " + ex.to_s)
ensure
@in_context = in_context_outer
@ -1873,6 +1874,7 @@ CODE
@in_context = func
return yield(*args)
rescue => ex
RBA::MacroExecutionContext::ignore_next_exception
raise("'" + func + "': " + ex.to_s)
ensure
@in_context = nil
@ -2097,7 +2099,7 @@ CODE
end
end
def _start(macro_path)
def _start(job_description)
# clearing the selection avoids some nasty problems
view = RBA::LayoutView::current
@ -2106,8 +2108,7 @@ CODE
@total_timer = RBA::Timer::new
@total_timer.start
@drc_progress = RBA::AbstractProgress::new("DRC: " + macro_path)
@drc_progress = RBA::AbstractProgress::new(job_description)
end
@ -2249,6 +2250,7 @@ CODE
@netter_data = nil
if final
@total_timer.stop
if @verbose
mem = RBA::Timer::memory_size
@ -2258,22 +2260,33 @@ CODE
info("Total elapsed: #{'%.3f'%(@total_timer.sys+@total_timer.user)}s")
end
end
end
if final && @log_file
@log_file.close
@log_file = nil
_cleanup
end
# force garbage collection
GC.start
# unlocks the UI
end
end
def _cleanup
if @log_file
@log_file.close
@log_file = nil
end
# unlocks the UI
if @drc_progress
@drc_progress._destroy
@drc_progress = nil
end
GC.start
end
def _take_data

View File

@ -17,33 +17,50 @@
<text>
module DRC
def DRC.execute_drc(macro, generator, rdb_index = nil)
class DRCExecutable &lt; RBA::Executable
drc = DRCEngine::new
drc._rdb_index = rdb_index
drc._generator = generator
drc._start(macro.path)
def initialize(macro, generator, rdb_index = nil)
begin
@drc = DRCEngine::new
@drc._rdb_index = rdb_index
@drc._generator = generator
@macro = macro
end
def execute
@drc._start("DRC: " + @macro.path)
# Set a debugger scope so that our errors end up with the debugger set to the DRC's line
RBA::MacroExecutionContext::set_debugger_scope(macro.path)
# No verbosity set in drc engine - we cannot use the engine's logger
RBA::Logger::verbosity &gt;= 10 &amp;&amp; RBA::Logger::info("Running #{macro.path}")
drc.instance_eval(macro.text, macro.path)
RBA::MacroExecutionContext::set_debugger_scope(@macro.path)
begin
# No verbosity set in drc engine - we cannot use the engine's logger
RBA::Logger::verbosity &gt;= 10 &amp;&amp; RBA::Logger::info("Running #{@macro.path}")
@drc.instance_eval(@macro.text, @macro.path)
rescue =&gt; ex
@drc.error("In #{@macro.path}: #{ex.to_s}")
RBA::MacroExecutionContext::ignore_next_exception
raise ex
end
nil
end
def cleanup
# Remove the debugger scope
RBA::MacroExecutionContext::remove_debugger_scope
rescue =&gt; ex
drc.error("In #{macro.path}: #{ex.to_s}")
RBA::MacroExecutionContext::ignore_next_exception
raise ex
ensure
# cleans up and creates layout and report views
drc._finish
@drc._finish
end
@ -70,11 +87,17 @@ module DRC
# create a template for the macro editor:
create_template(":/drc-templates/drc.lym")
# if available, create a menu branch
if RBA::Application::instance &amp;&amp; RBA::Application::instance.main_window
mw = RBA::Application::instance.main_window
mw.menu.insert_menu("tools_menu.verification_group+", "drc", "DRC")
end
end
# Implements the execute method
def execute(macro)
DRC::execute_drc(macro, @recipe.generator("script" => macro.path))
def executable(macro)
DRCExecutable::new(macro, @recipe.generator("script" => macro.path))
end
end
@ -100,20 +123,20 @@ module DRC
end
# Implements the execute method
def execute(macro)
DRC::execute_drc(macro, @recipe.generator("script" => macro.path))
def executable(macro)
DRCExecutable::new(macro, @recipe.generator("script" => macro.path))
end
end
# A recipe implementation allowing the LVS run to be redone
# A recipe implementation allowing the DRC run to be redone
class DRCRecipe &lt; RBA::Recipe
def initialize
super("drc", "DRC recipe")
end
def execute(params)
def executable(params)
script = params["script"]
if ! script
@ -123,7 +146,7 @@ module DRC
macro = RBA::Macro::macro_by_path(script)
macro || raise("Can't find DRC script #{script} - unable to re-run")
DRC::execute_drc(macro, self.generator("script" => script), params["rdb_index"])
DRCExecutable::new(macro, self.generator("script" => script), params["rdb_index"])
end

View File

@ -629,6 +629,72 @@ Class<tl::GlobPattern> decl_GlobPattern ("tl", "GlobPattern",
"This class has been introduced in version 0.26."
);
class Executable_Impl
: public tl::Executable, public gsi::ObjectBase
{
public:
Executable_Impl ()
: tl::Executable ()
{
// makes the object owned by the C++ side (registrar). This way we don't need to keep a
// singleton instance.
keep ();
}
virtual tl::Variant execute ()
{
if (execute_cb.can_issue ()) {
return execute_cb.issue<tl::Executable, tl::Variant> (&tl::Executable::execute);
} else {
return tl::Variant ();
}
}
virtual void cleanup ()
{
if (cleanup_cb.can_issue ()) {
cleanup_cb.issue<tl::Executable> (&tl::Executable::cleanup);
}
}
gsi::Callback execute_cb;
gsi::Callback cleanup_cb;
};
}
namespace tl
{
template <> struct type_traits<gsi::Executable_Impl> : public type_traits<tl::Executable> { };
}
namespace gsi
{
Class<tl::Executable> decl_Executable ("tl", "ExecutableBase",
gsi::Methods (),
"@hide"
);
Class<Executable_Impl> decl_Executable_Impl (decl_Executable, "tl", "Executable",
gsi::callback ("execute", &Executable_Impl::execute, &Executable_Impl::execute_cb,
"@brief Reimplement this method to provide the functionality of the executable.\n"
"This method is supposed to execute the operation with the given parameters and return the desired output."
) +
gsi::callback ("cleanup", &Executable_Impl::cleanup, &Executable_Impl::cleanup_cb,
"@brief Reimplement this method to provide post-mortem cleanup functionality.\n"
"This method is always called after execute terminated."
),
"@brief A generic executable object\n"
"This object is a delegate for implementing the actual function of some generic executable function. "
"In addition to the plain execution, if offers a post-mortem cleanup callback which is always executed, even "
"if execute's implementation is cancelled in the debugger.\n"
"\n"
"Parameters are kept as a generic key/value map.\n"
"\n"
"This class has been introduced in version 0.27."
);
class Recipe_Impl
: public tl::Recipe, public gsi::ObjectBase
{
@ -641,16 +707,16 @@ public:
keep ();
}
virtual tl::Variant execute (const std::map<std::string, tl::Variant> &params) const
virtual tl::Executable *executable (const std::map<std::string, tl::Variant> &params) const
{
if (execute_cb.can_issue ()) {
return execute_cb.issue<tl::Recipe, tl::Variant, const std::map<std::string, tl::Variant> &> (&tl::Recipe::execute, params);
if (executable_cb.can_issue ()) {
return executable_cb.issue<tl::Recipe, tl::Executable *, const std::map<std::string, tl::Variant> &> (&tl::Recipe::executable, params);
} else {
return tl::Variant ();
return 0;
}
}
gsi::Callback execute_cb;
gsi::Callback executable_cb;
};
}
@ -688,10 +754,13 @@ Class<Recipe_Impl> decl_Recipe_Impl ("tl", "Recipe",
"@brief Delivers the generator string from the given parameters.\n"
"The generator string can be used with \\make to re-run the recipe."
) +
gsi::callback ("execute", &Recipe_Impl::execute, &Recipe_Impl::execute_cb, gsi::arg ("params"),
"@brief Reimplement this method to provide the functionality of the recipe.\n"
"This method is supposed to re-run the recipe with the given parameters and deliver the "
"the intended output object."
gsi::callback ("executable", &Recipe_Impl::executable, &Recipe_Impl::executable_cb, gsi::arg ("params"),
"@brief Reimplement this method to provide an executable object for the actual implementation.\n"
"The reasoning behind this architecture is to supply a cleanup callback. This is useful when the "
"actual function is executed as a script and the script terminates in the debugger. The cleanup callback "
"allows implementing any kind of post-mortem action despite being cancelled in the debugger.\n"
"\n"
"This method has been introduced in version 0.27 and replaces 'execute'."
),
"@brief A facility for providing reproducable recipes\n"
"The idea of this facility is to provide a service by which an object\n"

View File

@ -3003,6 +3003,11 @@ MacroEditorDialog::translate_pseudo_id (size_t &file_id, int &line)
}
}
static void exit_from_macro ()
{
throw tl::ExitException ();
}
void
MacroEditorDialog::exception_thrown (gsi::Interpreter *interpreter, size_t file_id, int line, const std::string &eclass, const std::string &emsg, const gsi::StackTraceProvider *stack_trace_provider)
{
@ -3012,7 +3017,7 @@ MacroEditorDialog::exception_thrown (gsi::Interpreter *interpreter, size_t file_
}
if (!m_in_exec) {
throw tl::ExitException ();
exit_from_macro ();
}
// avoid recursive breakpoints and exception catches from the console while in a breakpoint or exception stop
@ -3098,7 +3103,7 @@ MacroEditorDialog::exception_thrown (gsi::Interpreter *interpreter, size_t file_
}
if (! m_in_exec) {
throw tl::ExitException ();
exit_from_macro ();
}
}
@ -3106,7 +3111,7 @@ void
MacroEditorDialog::trace (gsi::Interpreter *interpreter, size_t file_id, int line, const gsi::StackTraceProvider *stack_trace_provider)
{
if (!m_in_exec) {
throw tl::ExitException ();
exit_from_macro ();
}
// avoid recursive breakpoints and exception catches from the console while in a breakpoint or exception stop
@ -3157,7 +3162,7 @@ MacroEditorDialog::trace (gsi::Interpreter *interpreter, size_t file_id, int lin
}
if (! m_in_exec) {
throw tl::ExitException ();
exit_from_macro ();
}
} else if (++m_trace_count == 20) {
@ -3175,7 +3180,7 @@ MacroEditorDialog::trace (gsi::Interpreter *interpreter, size_t file_id, int lin
m_process_events_interval = std::max (0.05, std::min (2.0, (m_last_process_events - start).seconds () * 5.0));
if (!m_in_exec) {
throw tl::ExitException ();
exit_from_macro ();
}
}
@ -3590,6 +3595,9 @@ MacroEditorDialog::run (int stop_stack_depth, lym::Macro *macro)
} catch (tl::ExitException &) {
m_stop_stack_depth = -1;
// .. ignore exit exceptions ..
} catch (tl::BreakException &) {
m_stop_stack_depth = -1;
// .. ignore break exceptions ..
} catch (tl::ScriptError &re) {
m_stop_stack_depth = -1;
handle_error (re);

View File

@ -17,44 +17,52 @@
<text>
module LVS
def self.execute_lvs(macro, generator, l2ndb_index = nil)
class LVSExecutable &lt; RBA::Executable
timer = RBA::Timer::new
timer.start
lvs = LVSEngine::new
lvs._l2ndb_index = l2ndb_index
lvs._generator = generator
def initialize(macro, generator, l2ndb_index = nil)
lvs_progress = RBA::AbstractProgress::new("LVS: " + macro.path)
@lvs = LVSEngine::new
@lvs._l2ndb_index = l2ndb_index
@lvs._generator = generator
begin
# Set a debugger scope so that our errors end up with the debugger set to the LVS's line
RBA::MacroExecutionContext::set_debugger_scope(macro.path)
# No verbosity set in lvs engine - we cannot use the engine's logger
RBA::Logger::verbosity &gt;= 10 &amp;&amp; RBA::Logger::info("Running #{macro.path}")
lvs.instance_eval(macro.text, macro.path)
# Remove the debugger scope
RBA::MacroExecutionContext::remove_debugger_scope
rescue =&gt; ex
lvs.error("In #{macro.path}: #{ex.to_s}")
RBA::MacroExecutionContext::ignore_next_exception
raise ex
ensure
# cleans up and creates layout and report views
lvs._finish
# unlocks the UI
lvs_progress._destroy
@macro = macro
end
timer.stop
lvs.info("Total run time: #{'%.3f'%(timer.sys+timer.user)}s")
def execute
@lvs._start("LVS: " + @macro.path)
# Set a debugger scope so that our errors end up with the debugger set to the LVS's line
RBA::MacroExecutionContext::set_debugger_scope(@macro.path)
begin
# No verbosity set in lvs engine - we cannot use the engine's logger
RBA::Logger::verbosity &gt;= 10 &amp;&amp; RBA::Logger::info("Running #{@macro.path}")
@lvs.instance_eval(@macro.text, @macro.path)
rescue =&gt; ex
@lvs.error("In #{@macro.path}: #{ex.to_s}")
RBA::MacroExecutionContext::ignore_next_exception
raise ex
end
nil
end
def cleanup
# Remove the debugger scope
RBA::MacroExecutionContext::remove_debugger_scope
# cleans up and creates layout and report views
@lvs._finish
end
end
@ -88,8 +96,8 @@ module LVS
end
# Implements the execute method
def execute(macro)
LVS::execute_lvs(macro, @recipe.generator("script" => macro.path))
def executable(macro)
LVSExecutable::new(macro, @recipe.generator("script" => macro.path))
end
end
@ -115,8 +123,8 @@ module LVS
end
# Implements the execute method
def execute(macro)
LVS::execute_lvs(macro, @recipe.generator("script" => macro.path))
def executable(macro)
LVSExecutable::new(macro, @recipe.generator("script" => macro.path))
end
end
@ -128,7 +136,7 @@ module LVS
super("lvs", "LVS recipe")
end
def execute(params)
def executable(params)
script = params["script"]
if ! script
@ -138,7 +146,7 @@ module LVS
macro = RBA::Macro::macro_by_path(script)
macro || raise("Can't find LVS script #{script} - unable to re-run")
LVS::execute_lvs(macro, self.generator("script" => script), params["l2ndb_index"])
LVSExecutable::new(macro, self.generator("script" => script), params["l2ndb_index"])
end

View File

@ -142,10 +142,12 @@ public:
m_name = name;
}
virtual void execute (const lym::Macro *macro) const
virtual tl::Executable *executable (const lym::Macro *macro) const
{
if (f_execute.can_issue ()) {
f_execute.issue<MacroInterpreter, const lym::Macro *> (&MacroInterpreter::execute, macro);
if (f_executable.can_issue ()) {
return f_executable.issue<MacroInterpreter, tl::Executable *, const lym::Macro *> (&MacroInterpreter::executable, macro);
} else {
return 0;
}
}
@ -240,7 +242,7 @@ public:
}
}
gsi::Callback f_execute;
gsi::Callback f_executable;
private:
tl::RegisteredClass <lym::MacroInterpreter> *mp_registration;
@ -378,13 +380,15 @@ Class<gsi::MacroInterpreter> decl_MacroInterpreter ("lay", "MacroInterpreter",
"Before version 0.25 this attribute was a reimplementable method. It has been turned into an attribute for "
"performance reasons in version 0.25.\n"
) +
gsi::callback ("execute", &gsi::MacroInterpreter::execute, &gsi::MacroInterpreter::f_execute, gsi::arg ("macro"),
"@brief Gets called to execute a macro\n"
"This method must be reimplemented to execute the macro. "
"The system will call this script when a macro with interpreter type 'dsl' and the "
"name of this interpreter is run."
gsi::callback ("executable", &gsi::MacroInterpreter::executable, &gsi::MacroInterpreter::f_executable, gsi::arg ("macro"),
"@brief Returns the executable object which implements the macro execution\n"
"This method must be reimplemented to return an \\Executable object for the actual implementation. "
"The system will use this function to execute the script when a macro with interpreter type 'dsl' and the "
"name of this interpreter is run.\n"
"\n"
"@param macro The macro to execute\n"
"\n"
"This method has been introduced in version 0.27 and replaces the 'execute' method.\n"
),
"@brief A custom interpreter for a DSL (domain specific language)\n"
"\n"
@ -413,6 +417,21 @@ Class<gsi::MacroInterpreter> decl_MacroInterpreter ("lay", "MacroInterpreter",
"just evaluates the script text:\n"
"\n"
"@code\n"
"class SimpleExecutable < RBA::Excutable\n"
"\n"
" # Constructor\n"
" def initialize(macro)\n"
" @macro = macro\n"
" end\n"
" \n"
" # Implements the execute method\n"
" def execute\n"
" eval(@macro.text, nil, @macro.path)"
" nil\n"
" end\n"
"\n"
"end\n"
"\n"
"class SimpleInterpreter < RBA::MacroInterpreter\n"
"\n"
" # Constructor\n"
@ -428,8 +447,8 @@ Class<gsi::MacroInterpreter> decl_MacroInterpreter ("lay", "MacroInterpreter",
" end\n"
" \n"
" # Implements the execute method\n"
" def execute(macro)\n"
" eval(macro.text, nil, macro.path)\n"
" def executable(macro)\n"
" SimpleExecutable::new(macro)\n"
" end\n"
"\n"
"end\n"

View File

@ -32,10 +32,10 @@
namespace lym
{
void
MacroInterpreter::execute (const lym::Macro *) const
tl::Executable *
MacroInterpreter::executable (const lym::Macro *) const
{
throw tl::Exception (tl::to_string (QObject::tr ("execute() implementation missing for DSL interpreter")));
throw tl::Exception (tl::to_string (QObject::tr ("executable() implementation missing for DSL interpreter")));
}
bool
@ -107,7 +107,10 @@ MacroInterpreter::execute_macro (const lym::Macro *macro)
std::pair<std::string, std::string> et = cls->include_expansion (macro);
if (et.first.empty () || et.first == macro->path ()) {
cls->execute (macro);
std::unique_ptr<tl::Executable> eo (cls->executable (macro));
if (eo.get ()) {
eo->do_execute ();
}
} else {
@ -116,7 +119,10 @@ MacroInterpreter::execute_macro (const lym::Macro *macro)
tmp_macro.assign (*macro);
tmp_macro.set_text (et.second);
tmp_macro.set_file_path (et.first);
cls->execute (&tmp_macro);
std::unique_ptr<tl::Executable> eo (cls->executable (&tmp_macro));
if (eo.get ()) {
eo->do_execute ();
}
}

View File

@ -26,6 +26,7 @@
#include "lymCommon.h"
#include "tlRecipe.h"
#include "gsiObject.h"
#include "tlClassRegistry.h"
@ -63,11 +64,11 @@ public:
}
/**
* @brief Executes the macro
* @brief Creates the executable for a macro
*
* This method must be reimplemented to provide the actual execution of the macro.
* The caller will delete the returned object.
*/
virtual void execute (const lym::Macro *macro) const;
virtual tl::Executable *executable (const lym::Macro *macro) const;
/**
* @brief Returns the storage scheme

View File

@ -22,10 +22,73 @@
#include "tlRecipe.h"
#include "tlString.h"
#include "tlLog.h"
#include "tlException.h"
#include <memory>
namespace tl
{
// --------------------------------------------------------------------------------------
// Executable implementation
Executable::Executable ()
{
// .. nothing yet ..
}
Executable::~Executable ()
{
// .. nothing yet ..
}
tl::Variant
Executable::do_execute ()
{
tl::Variant res;
try {
res = execute ();
do_cleanup ();
} catch (...) {
do_cleanup ();
throw;
}
return res;
}
void
Executable::do_cleanup ()
{
try {
cleanup ();
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
} catch (std::runtime_error &ex) {
tl::error << ex.what ();
} catch (...) {
// ignore exceptions here
}
}
tl::Variant
Executable::execute ()
{
// .. the default implementation does nothing ..
return tl::Variant ();
}
void
Executable::cleanup ()
{
// .. the default implementation does nothing ..
}
// --------------------------------------------------------------------------------------
// Recipe implementation
Recipe::Recipe (const std::string &name, const std::string &description)
: tl::RegisteredClass<tl::Recipe> (this, 0, name.c_str (), false)
{
@ -83,9 +146,14 @@ tl::Variant Recipe::make (const std::string &generator, const std::map<std::stri
if (! recipe_obj) {
return tl::Variant ();
} else {
return recipe_obj->execute (params);
}
std::unique_ptr<Executable> eo (recipe_obj->executable (params));
if (! eo.get ()) {
return tl::Variant ();
}
return eo->do_execute ();
}
} // namespace tl

View File

@ -32,6 +32,80 @@
namespace tl
{
/**
* @brief A base class for an executable item
*
* This is a little more than just a function: it also provides a post-mortem
* action (cleanup). This is useful for script-based applications: as the
* main thread can be terminated in the debugger through ExitException unconditionally,
* cleanup() provides a way to implement actions after such an event.
*/
class TL_PUBLIC Executable
{
public:
Executable ();
virtual ~Executable ();
/**
* @brief Runs the function with error handling and cleanup
*/
tl::Variant do_execute ();
/**
* @brief Runs the specific job
*/
virtual tl::Variant execute ();
/**
* @brief Called after the job terminated
*/
virtual void cleanup ();
private:
void do_cleanup ();
};
/**
* @brief Provides a convenience class for an executable with parameters
*/
class TL_PUBLIC ExecutableWithParameters
: public Executable
{
public:
ExecutableWithParameters (const std::map<std::string, tl::Variant> &params)
: m_params (params)
{ }
/**
* @brief An utility function to get a parameter
*/
template <class T>
static T get_value (const std::map<std::string, tl::Variant> &params, const std::string &pname, const T &def_value)
{
std::map<std::string, tl::Variant>::const_iterator p = params.find (pname);
if (p != params.end ()) {
const tl::Variant &v = p->second;
return v.to<T> ();
} else {
return def_value;
}
}
/**
* @brief Gets the parameters
*/
const std::map<std::string, tl::Variant> &parameters () const
{
return m_params;
}
private:
std::map<std::string, tl::Variant> m_params;
void do_cleanup ();
};
/**
* @brief A facility for providing reproducable recipes
*
@ -80,21 +154,6 @@ public:
return m_description;
}
/**
* @brief An utility function to get a parameters
*/
template <class T>
static T get_value (const std::map<std::string, tl::Variant> &params, const std::string &pname, const T &def_value)
{
std::map<std::string, tl::Variant>::const_iterator p = params.find (pname);
if (p != params.end ()) {
const tl::Variant &v = p->second;
return v.to<T> ();
} else {
return def_value;
}
}
/**
* @brief Serializes the given recipe
*/
@ -110,9 +169,11 @@ public:
static tl::Variant make (const std::string &generator, const std::map<std::string, tl::Variant> &params = std::map<std::string, tl::Variant> ());
/**
* @brief Recipe interface: executes the recipe with the given parameters
* @brief Returns the executable object which actually implements the action to take
*
* The returned object is deleted by the caller.
*/
virtual tl::Variant execute (const std::map<std::string, tl::Variant> &params) const = 0;
virtual Executable *executable (const std::map<std::string, tl::Variant> &params) const = 0;
private:
Recipe (const Recipe &) : tl::RegisteredClass<tl::Recipe> (this) { }

View File

@ -27,18 +27,40 @@
namespace {
class MyExecutable : public tl::ExecutableWithParameters
{
public:
static bool cleanup_called;
MyExecutable (const std::map<std::string, tl::Variant> &params)
: tl::ExecutableWithParameters (params)
{ }
tl::Variant execute ()
{
int a = get_value (parameters (), "A", 0);
double b = get_value (parameters (), "B", 0.0);
double c = get_value (parameters (), "C", 1.0);
bool crash = get_value (parameters (), "X", false);
if (crash) {
throw tl::Exception ("crashed");
}
return tl::Variant (b * a * c);
}
void cleanup ()
{
cleanup_called = true;
}
};
bool MyExecutable::cleanup_called = false;
class MyRecipe : public tl::Recipe
{
public:
MyRecipe () : tl::Recipe ("test_recipe", "description") { }
tl::Variant execute (const std::map<std::string, tl::Variant> &params) const
{
int a = get_value (params, "A", 0);
double b = get_value (params, "B", 0.0);
double c = get_value (params, "C", 1.0);
return tl::Variant (b * a * c);
}
tl::Executable *executable (const std::map<std::string, tl::Variant> &params) const { return new MyExecutable (params); }
};
static MyRecipe my_recipe;
@ -54,11 +76,24 @@ TEST(1)
std::string g = my_recipe.generator (params);
EXPECT_EQ (g, "test_recipe: A=#7,B=##6");
MyExecutable::cleanup_called = false;
tl::Variant res = tl::Recipe::make (g);
EXPECT_EQ (res.to_double (), 42.0);
EXPECT_EQ (MyExecutable::cleanup_called, true);
std::map<std::string, tl::Variant> padd;
padd["C"] = tl::Variant(1.5);
res = tl::Recipe::make (g, padd);
EXPECT_EQ (res.to_double (), 63.0);
MyExecutable::cleanup_called = false;
padd.clear ();
padd["X"] = tl::Variant(true);
try {
res = tl::Recipe::make (g, padd);
EXPECT_EQ (1, 0);
} catch (tl::Exception &ex) {
EXPECT_EQ (ex.msg (), "crashed");
}
EXPECT_EQ (MyExecutable::cleanup_called, true);
}