Implement support for arrays in command arguments

fixes #6709
This commit is contained in:
Gunnar Beutner 2014-11-26 20:43:42 +01:00 committed by Gunnar Beutner
parent f1d37f6aa4
commit 9dfa3d22d4
19 changed files with 209 additions and 41 deletions

View File

@ -736,6 +736,11 @@ object CheckCommand "nrpe" {
description = "Make socket timeouts return an UNKNOWN state instead of CRITICAL" description = "Make socket timeouts return an UNKNOWN state instead of CRITICAL"
} }
"-t" = "$nrpe_timeout$" "-t" = "$nrpe_timeout$"
"-a" = {
value = "$nrpe_arguments$"
repeat_key = false
order = 1
}
} }
vars.nrpe_address = "$address$" vars.nrpe_address = "$address$"

View File

@ -212,7 +212,7 @@ String Host::StateTypeToString(StateType type)
return "HARD"; return "HARD";
} }
bool Host::ResolveMacro(const String& macro, const CheckResult::Ptr&, String *result) const bool Host::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const
{ {
if (macro == "state") { if (macro == "state") {
*result = StateToString(GetState()); *result = StateToString(GetState());

View File

@ -63,7 +63,7 @@ public:
static StateType StateTypeFromString(const String& state); static StateType StateTypeFromString(const String& state);
static String StateTypeToString(StateType state); static String StateTypeToString(StateType state);
virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const; virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const;
protected: protected:
virtual void Stop(void); virtual void Stop(void);

View File

@ -202,10 +202,12 @@
%attribute %dictionary "arguments" { %attribute %dictionary "arguments" {
%attribute %string "*", %attribute %string "*",
%attribute %dictionary "*" { %attribute %dictionary "*" {
%attribute %string "key"
%attribute %string "value" %attribute %string "value"
%attribute %string "description" %attribute %string "description"
%attribute %number "required" %attribute %number "required"
%attribute %number "skip_key" %attribute %number "skip_key"
%attribute %number "repeat_key"
%attribute %string "set_if" %attribute %string "set_if"
%attribute %number "order" %attribute %number "order"
} }

View File

@ -147,7 +147,7 @@ String IcingaApplication::GetNodeName(void) const
return ScriptVariable::Get("NodeName"); return ScriptVariable::Get("NodeName");
} }
bool IcingaApplication::ResolveMacro(const String& macro, const CheckResult::Ptr&, String *result) const bool IcingaApplication::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const
{ {
double now = Utility::GetTime(); double now = Utility::GetTime();

View File

@ -50,7 +50,7 @@ public:
Dictionary::Ptr GetVars(void) const; Dictionary::Ptr GetVars(void) const;
String GetNodeName(void) const; String GetNodeName(void) const;
virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const; virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const;
bool GetEnableNotifications(void) const; bool GetEnableNotifications(void) const;
void SetEnableNotifications(bool enabled); void SetEnableNotifications(bool enabled);

View File

@ -66,7 +66,7 @@ Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolv
} }
bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers, bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers,
const CheckResult::Ptr& cr, String *result, bool *recursive_macro) const CheckResult::Ptr& cr, Value *result, bool *recursive_macro)
{ {
CONTEXT("Resolving macro '" + macro + "'"); CONTEXT("Resolving macro '" + macro + "'");
@ -92,12 +92,7 @@ bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resol
Dictionary::Ptr vars = dobj->GetVars(); Dictionary::Ptr vars = dobj->GetVars();
if (vars && vars->Contains(macro)) { if (vars && vars->Contains(macro)) {
Value value = vars->Get(macro); *result = vars->Get(macro);
if (value.IsObjectType<Array>())
value = Utility::Join(value, ';');
*result = value;
*recursive_macro = true; *recursive_macro = true;
return true; return true;
} }
@ -150,9 +145,6 @@ bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resol
tokens[0] == "notes") tokens[0] == "notes")
*recursive_macro = true; *recursive_macro = true;
if (ref.IsObjectType<Array>())
ref = Utility::Join(ref, ';');
*result = ref; *result = ref;
return true; return true;
} }
@ -161,7 +153,7 @@ bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resol
return false; return false;
} }
String MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers, Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
const CheckResult::Ptr& cr, String *missingMacro, const CheckResult::Ptr& cr, String *missingMacro,
const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros, const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
bool useResolvedMacros, int recursionLevel) bool useResolvedMacros, int recursionLevel)
@ -183,7 +175,7 @@ String MacroProcessor::InternalResolveMacros(const String& str, const ResolverLi
String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1); String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1);
String resolved_macro; Value resolved_macro;
bool recursive_macro; bool recursive_macro;
bool found; bool found;
@ -211,10 +203,24 @@ String MacroProcessor::InternalResolveMacros(const String& str, const ResolverLi
} }
/* recursively resolve macros in the macro if it was a user macro */ /* recursively resolve macros in the macro if it was a user macro */
if (recursive_macro) if (recursive_macro) {
if (resolved_macro.IsObjectType<Array>()) {
Array::Ptr arr = resolved_macro;
Array::Ptr result = new Array();
ObjectLock olock(arr);
BOOST_FOREACH(Value& value, arr) {
result->Add(InternalResolveMacros(value,
resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
false, recursionLevel + 1));
}
resolved_macro = result;
} else
resolved_macro = InternalResolveMacros(resolved_macro, resolved_macro = InternalResolveMacros(resolved_macro,
resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(), resolvers, cr, missingMacro, EscapeCallback(), Dictionary::Ptr(),
false, recursionLevel + 1); false, recursionLevel + 1);
}
if (!useResolvedMacros && found && resolvedMacros) if (!useResolvedMacros && found && resolvedMacros)
resolvedMacros->Set(name, resolved_macro); resolvedMacros->Set(name, resolved_macro);
@ -222,8 +228,19 @@ String MacroProcessor::InternalResolveMacros(const String& str, const ResolverLi
if (escapeFn) if (escapeFn)
resolved_macro = escapeFn(resolved_macro); resolved_macro = escapeFn(resolved_macro);
/* we're done if the value is an array */
if (resolved_macro.IsObjectType<Array>()) {
/* don't allow mixing strings and arrays in macro strings */
if (pos_first != 0 || pos_second != str.GetLength() - 1)
BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
return resolved_macro;
}
String resolved_macro_str = resolved_macro;
result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro); result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro);
offset = pos_first + resolved_macro.GetLength() + 1; offset = pos_first + resolved_macro_str.GetLength() + 1;
} }
return result; return result;

View File

@ -37,7 +37,7 @@ namespace icinga
class I2_ICINGA_API MacroProcessor class I2_ICINGA_API MacroProcessor
{ {
public: public:
typedef boost::function<String (const String&)> EscapeCallback; typedef boost::function<Value (const Value&)> EscapeCallback;
typedef std::pair<String, Object::Ptr> ResolverSpec; typedef std::pair<String, Object::Ptr> ResolverSpec;
typedef std::vector<ResolverSpec> ResolverList; typedef std::vector<ResolverSpec> ResolverList;
@ -51,8 +51,8 @@ private:
MacroProcessor(void); MacroProcessor(void);
static bool ResolveMacro(const String& macro, const ResolverList& resolvers, static bool ResolveMacro(const String& macro, const ResolverList& resolvers,
const CheckResult::Ptr& cr, String *result, bool *recursive_macro); const CheckResult::Ptr& cr, Value *result, bool *recursive_macro);
static String InternalResolveMacros(const String& str, static Value InternalResolveMacros(const String& str,
const ResolverList& resolvers, const CheckResult::Ptr& cr, const ResolverList& resolvers, const CheckResult::Ptr& cr,
String *missingMacro, const EscapeCallback& escapeFn, String *missingMacro, const EscapeCallback& escapeFn,
const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros,

View File

@ -38,7 +38,7 @@ class I2_ICINGA_API MacroResolver
public: public:
DECLARE_PTR_TYPEDEFS(MacroResolver); DECLARE_PTR_TYPEDEFS(MacroResolver);
virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const = 0; virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const = 0;
}; };
} }

View File

@ -36,12 +36,13 @@ struct CommandArgument
{ {
int Order; int Order;
bool SkipKey; bool SkipKey;
bool RepeatKey;
bool SkipValue; bool SkipValue;
String Key; String Key;
String Value; Value AValue;
CommandArgument(void) CommandArgument(void)
: Order(0), SkipKey(false), SkipValue(false) : Order(0), SkipKey(false), RepeatKey(true), SkipValue(false)
{ } { }
bool operator<(const CommandArgument& rhs) const bool operator<(const CommandArgument& rhs) const
@ -50,6 +51,35 @@ struct CommandArgument
} }
}; };
void PluginUtility::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value, bool add_key, bool add_value)
{
if (add_key)
args->Add(key);
if (add_value)
args->Add(value);
}
Value PluginUtility::EscapeMacroShellArg(const Value& value)
{
String result;
if (value.IsObjectType<Array>()) {
Array::Ptr arr = value;
ObjectLock olock(arr);
BOOST_FOREACH(const Value& arg, arr) {
if (result.GetLength() > 0)
result += " ";
result += Utility::EscapeShellArg(arg);
}
} else
result = Utility::EscapeShellArg(value);
return result;
}
void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkable::Ptr& checkable, void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkable::Ptr& checkable,
const CheckResult::Ptr& cr, const MacroProcessor::ResolverList& macroResolvers, const CheckResult::Ptr& cr, const MacroProcessor::ResolverList& macroResolvers,
const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros,
@ -61,7 +91,7 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
Value command; Value command;
if (!raw_arguments || raw_command.IsObjectType<Array>()) if (!raw_arguments || raw_command.IsObjectType<Array>())
command = MacroProcessor::ResolveMacros(raw_command, macroResolvers, cr, NULL, command = MacroProcessor::ResolveMacros(raw_command, macroResolvers, cr, NULL,
Utility::EscapeShellArg, resolvedMacros, useResolvedMacros); PluginUtility::EscapeMacroShellArg, resolvedMacros, useResolvedMacros);
else { else {
Array::Ptr arr = new Array(); Array::Ptr arr = new Array();
arr->Add(raw_command); arr->Add(raw_command);
@ -83,10 +113,14 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
if (arginfo.IsObjectType<Dictionary>()) { if (arginfo.IsObjectType<Dictionary>()) {
Dictionary::Ptr argdict = arginfo; Dictionary::Ptr argdict = arginfo;
if (argdict->Contains("key"))
arg.Key = argdict->Get("key");
argval = argdict->Get("value"); argval = argdict->Get("value");
if (argdict->Contains("required")) if (argdict->Contains("required"))
required = argdict->Get("required"); required = argdict->Get("required");
arg.SkipKey = argdict->Get("skip_key"); arg.SkipKey = argdict->Get("skip_key");
if (argdict->Contains("repeat_key"))
arg.RepeatKey = argdict->Get("repeat_key");
arg.Order = argdict->Get("order"); arg.Order = argdict->Get("order");
String set_if = argdict->Get("set_if"); String set_if = argdict->Get("set_if");
@ -118,7 +152,7 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
arg.SkipValue = true; arg.SkipValue = true;
String missingMacro; String missingMacro;
arg.Value = MacroProcessor::ResolveMacros(argval, macroResolvers, arg.AValue = MacroProcessor::ResolveMacros(argval, macroResolvers,
cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros, cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
useResolvedMacros); useResolvedMacros);
@ -152,11 +186,32 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
Array::Ptr command_arr = command; Array::Ptr command_arr = command;
BOOST_FOREACH(const CommandArgument& arg, args) { BOOST_FOREACH(const CommandArgument& arg, args) {
if (!arg.SkipKey) Array::Ptr arr;
command_arr->Add(arg.Key);
if (!arg.SkipValue) if (arg.AValue.IsString())
command_arr->Add(arg.Value); AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue);
else if (arg.AValue.IsObjectType<Array>())
arr = static_cast<Array::Ptr>(arg.AValue);
else
continue;
if (arr) {
bool first = true;
ObjectLock olock(arr);
BOOST_FOREACH(const Value& value, arr) {
bool add_key;
if (first) {
first = false;
add_key = !arg.SkipKey;
} else
add_key = !arg.SkipKey && arg.RepeatKey;
AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue);
}
}
} }
} }
@ -173,6 +228,9 @@ void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkab
NULL, MacroProcessor::EscapeCallback(), resolvedMacros, NULL, MacroProcessor::EscapeCallback(), resolvedMacros,
useResolvedMacros); useResolvedMacros);
if (value.IsObjectType<Array>())
value = Utility::Join(value, ';');
envMacros->Set(kv.first, value); envMacros->Set(kv.first, value);
} }
} }

View File

@ -52,6 +52,9 @@ public:
private: private:
PluginUtility(void); PluginUtility(void);
static void AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value, bool add_key, bool add_value);
static Value EscapeMacroShellArg(const Value& value);
}; };
} }

View File

@ -134,7 +134,7 @@ String Service::StateTypeToString(StateType type)
return "HARD"; return "HARD";
} }
bool Service::ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const bool Service::ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const
{ {
if (macro == "state") { if (macro == "state") {
*result = StateToString(GetState()); *result = StateToString(GetState());

View File

@ -43,7 +43,7 @@ public:
Host::Ptr GetHost(void) const; Host::Ptr GetHost(void) const;
virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, String *result) const; virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const;
static ServiceState StateFromString(const String& state); static ServiceState StateFromString(const String& state);
static String StateToString(ServiceState state); static String StateToString(ServiceState state);

View File

@ -117,11 +117,11 @@ void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C
String prefix; String prefix;
if (service) { if (service) {
prefix = MacroProcessor::ResolveMacros(GetServiceNameTemplate(), resolvers, cr, NULL, &GraphiteWriter::EscapeMetric); prefix = MacroProcessor::ResolveMacros(GetServiceNameTemplate(), resolvers, cr, NULL, &GraphiteWriter::EscapeMacroMetric);
SendMetric(prefix, "state", service->GetState()); SendMetric(prefix, "state", service->GetState());
} else { } else {
prefix = MacroProcessor::ResolveMacros(GetHostNameTemplate(), resolvers, cr, NULL, &GraphiteWriter::EscapeMetric); prefix = MacroProcessor::ResolveMacros(GetHostNameTemplate(), resolvers, cr, NULL, &GraphiteWriter::EscapeMacroMetric);
SendMetric(prefix, "state", host->GetState()); SendMetric(prefix, "state", host->GetState());
} }
@ -214,3 +214,19 @@ String GraphiteWriter::EscapeMetric(const String& str)
return result; return result;
} }
Value GraphiteWriter::EscapeMacroMetric(const Value& value)
{
if (value.IsObjectType<Array>()) {
Array::Ptr arr = value;
Array::Ptr result = new Array();
ObjectLock olock(arr);
BOOST_FOREACH(const Value& arg, arr) {
result->Add(EscapeMetric(arg));
}
return Utility::Join(result, '.');
} else
return EscapeMetric(value);
}

View File

@ -55,6 +55,7 @@ private:
void SendMetric(const String& prefix, const String& name, double value); void SendMetric(const String& prefix, const String& name, double value);
void SendPerfdata(const String& prefix, const CheckResult::Ptr& cr); void SendPerfdata(const String& prefix, const CheckResult::Ptr& cr);
static String EscapeMetric(const String& str); static String EscapeMetric(const String& str);
static Value EscapeMacroMetric(const Value& value);
void ReconnectTimerHandler(void); void ReconnectTimerHandler(void);
}; };

View File

@ -64,6 +64,14 @@ void PerfdataWriter::Start(void)
RotateFile(m_HostOutputFile, GetHostTempPath(), GetHostPerfdataPath()); RotateFile(m_HostOutputFile, GetHostTempPath(), GetHostPerfdataPath());
} }
Value PerfdataWriter::EscapeMacroMetric(const Value& value)
{
if (value.IsObjectType<Array>())
return Utility::Join(value, ';');
else
return value;
}
void PerfdataWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) void PerfdataWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
{ {
CONTEXT("Writing performance data for object '" + checkable->GetName() + "'"); CONTEXT("Writing performance data for object '" + checkable->GetName() + "'");
@ -86,7 +94,7 @@ void PerfdataWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C
resolvers.push_back(std::make_pair("icinga", IcingaApplication::GetInstance())); resolvers.push_back(std::make_pair("icinga", IcingaApplication::GetInstance()));
if (service) { if (service) {
String line = MacroProcessor::ResolveMacros(GetServiceFormatTemplate(), resolvers, cr); String line = MacroProcessor::ResolveMacros(GetServiceFormatTemplate(), resolvers, cr, NULL, &PerfdataWriter::EscapeMacroMetric);
{ {
ObjectLock olock(this); ObjectLock olock(this);
@ -96,7 +104,7 @@ void PerfdataWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C
m_ServiceOutputFile << line << "\n"; m_ServiceOutputFile << line << "\n";
} }
} else { } else {
String line = MacroProcessor::ResolveMacros(GetHostFormatTemplate(), resolvers, cr); String line = MacroProcessor::ResolveMacros(GetHostFormatTemplate(), resolvers, cr, NULL, &PerfdataWriter::EscapeMacroMetric);
{ {
ObjectLock olock(this); ObjectLock olock(this);

View File

@ -47,6 +47,7 @@ protected:
private: private:
void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr);
static Value EscapeMacroMetric(const Value& value);
Timer::Ptr m_RotationTimer; Timer::Ptr m_RotationTimer;
void RotationTimerHandler(void); void RotationTimerHandler(void);

View File

@ -22,7 +22,8 @@ set(base_test_SOURCES
base-json.cpp base-match.cpp base-netstring.cpp base-object.cpp base-json.cpp base-match.cpp base-netstring.cpp base-object.cpp
base-serialize.cpp base-shellescape.cpp base-stacktrace.cpp base-serialize.cpp base-shellescape.cpp base-stacktrace.cpp
base-stream.cpp base-string.cpp base-timer.cpp base-type.cpp base-stream.cpp base-string.cpp base-timer.cpp base-type.cpp
base-value.cpp config-ops.cpp icinga-perfdata.cpp test.cpp base-value.cpp config-ops.cpp icinga-macros.cpp icinga-perfdata.cpp
test.cpp
) )
set_property(SOURCE test.cpp PROPERTY EXCLUDE_UNITY_BUILD TRUE) set_property(SOURCE test.cpp PROPERTY EXCLUDE_UNITY_BUILD TRUE)
@ -90,6 +91,7 @@ add_boost_test(base
base_value/format base_value/format
config_ops/simple config_ops/simple
config_ops/advanced config_ops/advanced
icinga_macros/simple
icinga_perfdata/empty icinga_perfdata/empty
icinga_perfdata/simple icinga_perfdata/simple
icinga_perfdata/quotes icinga_perfdata/quotes

55
test/icinga-macros.cpp Normal file
View File

@ -0,0 +1,55 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program 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 2 *
* of the License, or (at your option) any later version. *
* *
* This program 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 this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "icinga/macroprocessor.hpp"
#include <boost/test/unit_test.hpp>
using namespace icinga;
BOOST_AUTO_TEST_SUITE(icinga_macros)
BOOST_AUTO_TEST_CASE(simple)
{
Dictionary::Ptr macrosA = new Dictionary();
macrosA->Set("testA", 7);
macrosA->Set("testB", "hello");
Dictionary::Ptr macrosB = new Dictionary();
macrosB->Set("testA", 3);
macrosB->Set("testC", "world");
Array::Ptr testD = new Array();
testD->Add(3);
testD->Add("test");
macrosB->Set("testD", testD);
MacroProcessor::ResolverList resolvers;
resolvers.push_back(std::make_pair("macrosA", macrosA));
resolvers.push_back(std::make_pair("macrosB", macrosB));
BOOST_CHECK(MacroProcessor::ResolveMacros("$macrosA.testB$ $macrosB.testC$", resolvers) == "hello world");
BOOST_CHECK(MacroProcessor::ResolveMacros("$testA$", resolvers) == "7");
Array::Ptr result = MacroProcessor::ResolveMacros("$testD$", resolvers);
BOOST_CHECK(result->GetLength() == 2);
}
BOOST_AUTO_TEST_SUITE_END()