Merge pull request #979 from joakim-hove/actionx-dynamic
Convert Schedule::Actions to a DynamicState<Actions>
This commit is contained in:
@@ -532,6 +532,7 @@ if(ENABLE_ECL_INPUT)
|
||||
opm/parser/eclipse/EclipseState/Schedule/Action/Actdims.hpp
|
||||
opm/parser/eclipse/EclipseState/Schedule/Action/Actions.hpp
|
||||
opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp
|
||||
opm/parser/eclipse/EclipseState/Schedule/Action/Condition.hpp
|
||||
opm/parser/eclipse/EclipseState/Schedule/ArrayDimChecker.hpp
|
||||
opm/parser/eclipse/EclipseState/Schedule/TimeMap.hpp
|
||||
opm/parser/eclipse/EclipseState/Schedule/VFPInjTable.hpp
|
||||
|
||||
@@ -57,7 +57,7 @@ void msim::run(Schedule& schedule, EclipseIO& io, bool report_only) {
|
||||
|
||||
|
||||
void msim::post_step(Schedule& schedule, const SummaryState& st, data::Solution& /* sol */, data::Wells& /* well_data */, size_t report_step) const {
|
||||
const auto& actions = schedule.actions();
|
||||
const auto& actions = schedule.actions(report_step);
|
||||
if (actions.empty())
|
||||
return;
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace Opm {
|
||||
void setLocation(const std::pair<const std::string&, std::size_t>& location);
|
||||
const std::string& getFileName() const;
|
||||
int getLineNumber() const;
|
||||
std::pair<std::string, std::size_t> location() const;
|
||||
|
||||
|
||||
size_t size() const;
|
||||
void addRecord(DeckRecord&& record);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
but eliminates the memory saving DynamicState is intended to enable. You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Action/ActionAST.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Action/ActionResult.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Action/Condition.hpp>
|
||||
|
||||
|
||||
|
||||
namespace Opm {
|
||||
class DeckKeyword;
|
||||
@@ -76,6 +79,13 @@ public:
|
||||
std::vector<DeckKeyword>::const_iterator begin() const;
|
||||
std::vector<DeckKeyword>::const_iterator end() const;
|
||||
static bool valid_keyword(const std::string& keyword);
|
||||
|
||||
/*
|
||||
The conditions() and keyword_strings() methods, and their underlying data
|
||||
members are only present to support writing formatted restart files.
|
||||
*/
|
||||
const std::vector<Condition>& conditions() const;
|
||||
std::vector<std::string> keyword_strings() const;
|
||||
private:
|
||||
std::string m_name;
|
||||
size_t m_max_run = 0;
|
||||
@@ -84,6 +94,7 @@ private:
|
||||
|
||||
std::vector<DeckKeyword> keywords;
|
||||
Action::AST condition;
|
||||
std::vector<Condition> m_conditions;
|
||||
mutable size_t run_count = 0;
|
||||
mutable std::time_t last_run = 0;
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp>
|
||||
|
||||
@@ -42,10 +42,14 @@ public:
|
||||
bool empty() const;
|
||||
void add(const ActionX& action);
|
||||
bool ready(std::time_t sim_time) const;
|
||||
Action::ActionX& at(const std::string& name);
|
||||
const ActionX& get(const std::string& name) const;
|
||||
const ActionX& get(std::size_t index) const;
|
||||
std::vector<const ActionX *> pending(std::time_t sim_time) const;
|
||||
|
||||
std::vector<ActionX>::const_iterator begin() const;
|
||||
std::vector<ActionX>::const_iterator end() const;
|
||||
private:
|
||||
std::map<std::string, ActionX> actions;
|
||||
std::vector<ActionX> actions;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2019 Equinor ASA.
|
||||
|
||||
This file is part of the Open Porous Media project (OPM).
|
||||
|
||||
OPM is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
OPM is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ACTIONX_CONDITION_HPP
|
||||
#define ACTIONX_CONDITION_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Opm {
|
||||
namespace Action {
|
||||
|
||||
class Condition {
|
||||
public:
|
||||
|
||||
enum class Logical {
|
||||
AND,
|
||||
OR,
|
||||
END
|
||||
};
|
||||
|
||||
enum class Comparator {
|
||||
EQUAL,
|
||||
GREATER,
|
||||
LESS,
|
||||
INVALID
|
||||
};
|
||||
|
||||
|
||||
void add_token(const std::string& token) {
|
||||
if (!this->expression.empty())
|
||||
this->expression += " ";
|
||||
this->expression += token;
|
||||
}
|
||||
|
||||
Condition(const std::string& quant) :
|
||||
quantity(quant)
|
||||
{}
|
||||
|
||||
|
||||
std::string expression;
|
||||
std::string quantity;
|
||||
Logical logic = Logical::END;
|
||||
Comparator cmp = Comparator::INVALID;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -183,7 +183,7 @@ namespace Opm
|
||||
const WellTestConfig& wtestConfig(size_t timestep) const;
|
||||
const WListManager& getWListManager(size_t timeStep) const;
|
||||
const UDQConfig& getUDQConfig(size_t timeStep) const;
|
||||
const Action::Actions& actions() const;
|
||||
const Action::Actions& actions(std::size_t timeStep) const;
|
||||
void evalAction(const SummaryState& summary_state, size_t timeStep);
|
||||
|
||||
GTNode groupTree(std::size_t report_step) const;
|
||||
@@ -235,9 +235,9 @@ namespace Opm
|
||||
DynamicState<std::shared_ptr<UDQActive>> udq_active;
|
||||
DynamicState<std::shared_ptr<GuideRateModel>> guide_rate_model;
|
||||
DynamicState<WellProducer::ControlModeEnum> global_whistctl_mode;
|
||||
DynamicState<std::shared_ptr<Action::Actions>> m_actions;
|
||||
RFTConfig rft_config;
|
||||
|
||||
Action::Actions m_actions;
|
||||
|
||||
std::map<std::string,Events> well_events;
|
||||
DynamicState<int> m_nupcol;
|
||||
@@ -250,6 +250,7 @@ namespace Opm
|
||||
void addWellToGroup( const std::string& group_name, const std::string& well_name , size_t timeStep);
|
||||
void iterateScheduleSection(const ParseContext& parseContext , ErrorGuard& errors, const SCHEDULESection& , const EclipseGrid& grid,
|
||||
const Eclipse3DProperties& eclipseProperties);
|
||||
void addACTIONX(const Action::ActionX& action, std::size_t currentStep);
|
||||
void addGroupToGroup( const std::string& parent_group, const std::string& child_group, size_t timeStep);
|
||||
void addGroupToGroup( const std::string& parent_group, const Group2& child_group, size_t timeStep);
|
||||
void addGroup(const std::string& groupName , size_t timeStep, const UnitSystem& unit_system);
|
||||
|
||||
@@ -60,6 +60,10 @@ namespace Opm {
|
||||
return m_lineNumber;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::size_t> DeckKeyword::location() const {
|
||||
return std::make_pair( this->getFileName(), this->getLineNumber() );
|
||||
}
|
||||
|
||||
void DeckKeyword::setDataKeyword(bool isDataKeyword_) {
|
||||
m_isDataKeyword = isDataKeyword_;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ Parser::Parser(const std::vector<std::string>& tokens_arg) :
|
||||
{}
|
||||
|
||||
|
||||
TokenType Parser::get_type(const std::string& arg) const {
|
||||
TokenType Parser::get_type(const std::string& arg) {
|
||||
std::string lower_arg = arg;
|
||||
std::for_each(lower_arg.begin(),
|
||||
lower_arg.end(),
|
||||
|
||||
@@ -50,11 +50,11 @@ struct ParseNode {
|
||||
class Parser {
|
||||
public:
|
||||
static Action::ASTNode parse(const std::vector<std::string>& tokens);
|
||||
static TokenType get_type(const std::string& arg);
|
||||
|
||||
private:
|
||||
explicit Parser(const std::vector<std::string>& tokens);
|
||||
|
||||
TokenType get_type(const std::string& arg) const;
|
||||
ParseNode current() const;
|
||||
ParseNode next();
|
||||
size_t pos() const;
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <opm/parser/eclipse/Deck/DeckKeyword.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp>
|
||||
|
||||
#include "ActionValue.hpp"
|
||||
#include "ActionParser.hpp"
|
||||
|
||||
namespace Opm {
|
||||
namespace Action {
|
||||
@@ -57,8 +59,39 @@ ActionX::ActionX(const DeckKeyword& kw, std::time_t start_time) :
|
||||
std::vector<std::string> tokens;
|
||||
for (size_t record_index = 1; record_index < kw.size(); record_index++) {
|
||||
const auto& record = kw.getRecord(record_index);
|
||||
for (const auto& token : record.getItem("CONDITION").getData<std::string>())
|
||||
const auto& cond_tokens = record.getItem("CONDITION").getData<std::string>();
|
||||
Condition cond(cond_tokens[0]);
|
||||
|
||||
for (const auto& token : cond_tokens) {
|
||||
tokens.push_back(token);
|
||||
cond.add_token(token);
|
||||
|
||||
{
|
||||
auto token_type = Parser::get_type(token);
|
||||
if (token_type == TokenType::op_eq)
|
||||
cond.cmp = Condition::Comparator::EQUAL;
|
||||
|
||||
if (token_type == TokenType::op_gt)
|
||||
cond.cmp = Condition::Comparator::GREATER;
|
||||
|
||||
if (token_type == TokenType::op_lt)
|
||||
cond.cmp = Condition::Comparator::LESS;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto token_type = Parser::get_type(cond_tokens.back());
|
||||
if (token_type == TokenType::op_and)
|
||||
cond.logic = Condition::Logical::AND;
|
||||
else if (token_type == TokenType::op_or)
|
||||
cond.logic = Condition::Logical::OR;
|
||||
}
|
||||
|
||||
if (cond.cmp == Condition::Comparator::INVALID) {
|
||||
const auto& location = kw.location();
|
||||
throw std::invalid_argument("Could not determine comparison type for ACTIONX keyword at " + location.first + ":" + std::to_string(location.second));
|
||||
}
|
||||
this->m_conditions.push_back(std::move(cond));
|
||||
}
|
||||
this->condition = Action::AST(tokens);
|
||||
}
|
||||
@@ -110,5 +143,39 @@ std::vector<DeckKeyword>::const_iterator ActionX::end() const {
|
||||
return this->keywords.end();
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::string> ActionX::keyword_strings() const {
|
||||
std::vector<std::string> keyword_strings;
|
||||
std::string keyword_string;
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
for (const auto& kw : this->keywords)
|
||||
ss << kw;
|
||||
|
||||
keyword_string = ss.str();
|
||||
}
|
||||
|
||||
std::size_t offset = 0;
|
||||
while (true) {
|
||||
auto eol_pos = keyword_string.find('\n', offset);
|
||||
if (eol_pos == std::string::npos)
|
||||
break;
|
||||
|
||||
if (eol_pos > offset)
|
||||
keyword_strings.push_back(keyword_string.substr(offset, eol_pos - offset));
|
||||
|
||||
offset = eol_pos + 1;
|
||||
}
|
||||
keyword_strings.push_back("ENDACTIO");
|
||||
|
||||
return keyword_strings;
|
||||
}
|
||||
|
||||
|
||||
const std::vector<Condition>& ActionX::conditions() const {
|
||||
return this->m_conditions;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with OPM. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <algorithm>
|
||||
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Action/Actions.hpp>
|
||||
#include <opm/parser/eclipse/EclipseState/Schedule/Action/ActionX.hpp>
|
||||
@@ -34,21 +35,34 @@ bool Actions::empty() const {
|
||||
|
||||
|
||||
void Actions::add(const ActionX& action) {
|
||||
auto iter = this->actions.find(action.name());
|
||||
if (iter != this->actions.end())
|
||||
this->actions.erase(iter);
|
||||
|
||||
this->actions.insert(std::pair<std::string,ActionX>(action.name(), action));
|
||||
printf("Adding: %s\n", action.name().c_str());
|
||||
auto iter = std::find_if( this->actions.begin(), this->actions.end(), [&action](const ActionX& arg) { return arg.name() == action.name(); });
|
||||
if (iter == this->actions.end()) {
|
||||
printf("push_back\n");
|
||||
this->actions.push_back(action);
|
||||
} else {
|
||||
printf("update\n");
|
||||
*iter = action;
|
||||
}
|
||||
}
|
||||
|
||||
ActionX& Actions::at(const std::string& name) {
|
||||
return this->actions.at(name);
|
||||
|
||||
const ActionX& Actions::get(const std::string& name) const {
|
||||
const auto iter = std::find_if( this->actions.begin(), this->actions.end(), [&name](const ActionX& action) { return action.name() == name; });
|
||||
if (iter == this->actions.end())
|
||||
throw std::range_error("No such action: " + name);
|
||||
|
||||
return *iter;
|
||||
}
|
||||
|
||||
const ActionX& Actions::get(std::size_t index) const {
|
||||
return this->actions[index];
|
||||
}
|
||||
|
||||
|
||||
bool Actions::ready(std::time_t sim_time) const {
|
||||
for (const auto& pair : this->actions) {
|
||||
if (pair.second.ready(sim_time))
|
||||
for (const auto& action : this->actions) {
|
||||
if (action.ready(sim_time))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -57,13 +71,20 @@ bool Actions::ready(std::time_t sim_time) const {
|
||||
|
||||
std::vector<const ActionX *> Actions::pending(std::time_t sim_time) const {
|
||||
std::vector<const ActionX *> action_vector;
|
||||
for (auto& pair : this->actions) {
|
||||
auto& action = pair.second;
|
||||
for (const auto& action : this->actions) {
|
||||
if (action.ready(sim_time))
|
||||
action_vector.push_back( &action );
|
||||
}
|
||||
return action_vector;
|
||||
}
|
||||
|
||||
std::vector<ActionX>::const_iterator Actions::begin() const {
|
||||
return this->actions.begin();
|
||||
}
|
||||
|
||||
std::vector<ActionX>::const_iterator Actions::end() const {
|
||||
return this->actions.end();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ namespace {
|
||||
udq_active(this->m_timeMap, std::make_shared<UDQActive>()),
|
||||
guide_rate_model(this->m_timeMap, std::make_shared<GuideRateModel>()),
|
||||
global_whistctl_mode(this->m_timeMap, WellProducer::CMODE_UNDEFINED),
|
||||
m_actions(this->m_timeMap, std::make_shared<Action::Actions>()),
|
||||
rft_config(this->m_timeMap),
|
||||
m_nupcol(this->m_timeMap, 3)
|
||||
{
|
||||
@@ -429,7 +430,7 @@ namespace {
|
||||
parseContext.handleError( ParseContext::ACTIONX_ILLEGAL_KEYWORD, msg, errors);
|
||||
}
|
||||
}
|
||||
this->m_actions.add(action);
|
||||
this->addACTIONX(action, currentStep);
|
||||
} else
|
||||
this->handleKeyword(currentStep, section, keywordIdx, keyword, parseContext, errors, grid, eclipseProperties, unit_system, rftProperties);
|
||||
|
||||
@@ -454,6 +455,13 @@ namespace {
|
||||
}
|
||||
|
||||
|
||||
void Schedule::addACTIONX(const Action::ActionX& action, std::size_t currentStep) {
|
||||
auto new_actions = std::make_shared<Action::Actions>( this->actions(currentStep) );
|
||||
new_actions->add(action);
|
||||
this->m_actions.update(currentStep, new_actions);
|
||||
}
|
||||
|
||||
|
||||
void Schedule::checkUnhandledKeywords(const SCHEDULESection& /*section*/) const
|
||||
{
|
||||
}
|
||||
@@ -2560,8 +2568,9 @@ void Schedule::handleGRUPTREE( const DeckKeyword& keyword, size_t currentStep, c
|
||||
}
|
||||
|
||||
|
||||
const Action::Actions& Schedule::actions() const {
|
||||
return this->m_actions;
|
||||
const Action::Actions& Schedule::actions(std::size_t timeStep) const {
|
||||
const auto& ptr = this->m_actions.get(timeStep);
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -595,3 +595,124 @@ BOOST_AUTO_TEST_CASE(TestFieldAND) {
|
||||
BOOST_CHECK_EQUAL(wells[0], "OP3");
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(SCAN2) {
|
||||
const auto deck_string = std::string{ R"(
|
||||
SCHEDULE
|
||||
|
||||
TSTEP
|
||||
10 /
|
||||
|
||||
ACTIONX
|
||||
'B' /
|
||||
WWCT 'OPX' > 0.75 AND / -- The spaces will/should be normalized in Condition::expression()
|
||||
FPR < 100 /
|
||||
/
|
||||
|
||||
WELSPECS
|
||||
'W1' 'OP' 1 1 3.33 'OIL' 7*/
|
||||
/
|
||||
|
||||
ENDACTIO
|
||||
|
||||
TSTEP
|
||||
10 /
|
||||
|
||||
|
||||
ACTIONX
|
||||
'A' /
|
||||
WOPR 'OPX' = 1000 /
|
||||
/
|
||||
|
||||
ENDACTIO
|
||||
|
||||
ACTIONX
|
||||
'B' /
|
||||
FWCT < 0.50 /
|
||||
/
|
||||
|
||||
|
||||
|
||||
ENDACTIO
|
||||
|
||||
TSTEP
|
||||
10 /
|
||||
|
||||
)"};
|
||||
|
||||
Opm::Parser parser;
|
||||
auto deck = parser.parseString(deck_string);
|
||||
EclipseGrid grid1(10,10,10);
|
||||
TableManager table ( deck );
|
||||
Eclipse3DProperties eclipseProperties ( deck , table, grid1);
|
||||
Runspec runspec (deck);
|
||||
Schedule sched(deck, grid1, eclipseProperties, runspec);
|
||||
const auto& actions0 = sched.actions(0);
|
||||
BOOST_CHECK_EQUAL(actions0.size(), 0);
|
||||
|
||||
const auto& actions1 = sched.actions(1);
|
||||
BOOST_CHECK_EQUAL(actions1.size(), 1);
|
||||
|
||||
|
||||
const auto& act1 = actions1.get("B");
|
||||
const auto& strings = act1.keyword_strings();
|
||||
BOOST_CHECK_EQUAL(strings.size(), 4);
|
||||
BOOST_CHECK_EQUAL(strings.back(), "ENDACTIO");
|
||||
|
||||
|
||||
std::string rdeck_string = "";
|
||||
for (std::size_t i = 0; i < strings.size(); i++)
|
||||
rdeck_string += strings[i] + "\n";
|
||||
|
||||
auto deck2 = parser.parseString(rdeck_string);
|
||||
BOOST_CHECK(deck2.getKeyword("WELSPECS") == deck.getKeyword("WELSPECS"));
|
||||
|
||||
|
||||
const auto& conditions = act1.conditions();
|
||||
BOOST_CHECK_EQUAL(conditions.size() , 2);
|
||||
|
||||
const auto& cond0 = conditions[0];
|
||||
BOOST_CHECK_EQUAL(cond0.expression, "WWCT 'OPX' > 0.75 AND");
|
||||
BOOST_CHECK_EQUAL(cond0.quantity, "WWCT");
|
||||
BOOST_CHECK(cond0.cmp == Action::Condition::Comparator::GREATER);
|
||||
BOOST_CHECK(cond0.logic == Action::Condition::Logical::AND);
|
||||
|
||||
const auto& cond1 = conditions[1];
|
||||
BOOST_CHECK_EQUAL(cond1.expression, "FPR < 100");
|
||||
BOOST_CHECK_EQUAL(cond1.quantity, "FPR");
|
||||
BOOST_CHECK(cond1.cmp == Action::Condition::Comparator::LESS);
|
||||
BOOST_CHECK(cond1.logic == Action::Condition::Logical::END);
|
||||
|
||||
/*****************************************************************/
|
||||
|
||||
const auto& actions2 = sched.actions(2);
|
||||
BOOST_CHECK_EQUAL(actions2.size(), 2);
|
||||
|
||||
const auto& actB = actions2.get("B");
|
||||
const auto& condB = actB.conditions();
|
||||
BOOST_CHECK_EQUAL(condB.size() , 1);
|
||||
BOOST_CHECK_EQUAL(condB[0].expression, "FWCT < 0.50");
|
||||
BOOST_CHECK_EQUAL(condB[0].quantity, "FWCT");
|
||||
BOOST_CHECK(condB[0].cmp == Action::Condition::Comparator::LESS);
|
||||
BOOST_CHECK(condB[0].logic == Action::Condition::Logical::END);
|
||||
|
||||
const auto& actA = actions2.get("A");
|
||||
const auto& condA = actA.conditions();
|
||||
BOOST_CHECK_EQUAL(condA.size() , 1);
|
||||
BOOST_CHECK_EQUAL(condA[0].expression, "WOPR 'OPX' = 1000");
|
||||
BOOST_CHECK_EQUAL(condA[0].quantity, "WOPR");
|
||||
BOOST_CHECK(condA[0].cmp == Action::Condition::Comparator::EQUAL);
|
||||
BOOST_CHECK(condA[0].logic == Action::Condition::Logical::END);
|
||||
|
||||
std::size_t index = 0;
|
||||
for (const auto& act : actions2) {
|
||||
if (index == 0)
|
||||
BOOST_CHECK_EQUAL("B", act.name());
|
||||
|
||||
if (index == 1)
|
||||
BOOST_CHECK_EQUAL("A", act.name());
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user