context_unittest.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <eval/token.h>
  16. #include <eval/eval_context.h>
  17. #include <eval/token.h>
  18. #include <dhcp/option.h>
  19. #include <dhcp/pkt4.h>
  20. #include <boost/shared_ptr.hpp>
  21. #include <boost/scoped_ptr.hpp>
  22. #include <gtest/gtest.h>
  23. using namespace std;
  24. using namespace isc::dhcp;
  25. namespace {
  26. /// @brief Test class for testing EvalContext aka class test parsing
  27. class EvalContextTest : public ::testing::Test {
  28. public:
  29. /// @brief constructor to initialize members
  30. EvalContextTest() : ::testing::Test(),
  31. universe_(Option::V4), parsed_(false)
  32. { }
  33. /// @brief checks if the given token is a string with the expected value
  34. void checkTokenString(const TokenPtr& token, const std::string& expected) {
  35. ASSERT_TRUE(token);
  36. boost::shared_ptr<TokenString> str =
  37. boost::dynamic_pointer_cast<TokenString>(token);
  38. ASSERT_TRUE(str);
  39. Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
  40. ValueStack values;
  41. EXPECT_NO_THROW(token->evaluate(*pkt4, values));
  42. ASSERT_EQ(1, values.size());
  43. EXPECT_EQ(expected, values.top());
  44. }
  45. /// @brief checks if the given token is a hex string with the expected value
  46. void checkTokenHexString(const TokenPtr& token,
  47. const std::string& expected) {
  48. ASSERT_TRUE(token);
  49. boost::shared_ptr<TokenHexString> hex =
  50. boost::dynamic_pointer_cast<TokenHexString>(token);
  51. ASSERT_TRUE(hex);
  52. Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
  53. ValueStack values;
  54. EXPECT_NO_THROW(token->evaluate(*pkt4, values));
  55. ASSERT_EQ(1, values.size());
  56. EXPECT_EQ(expected, values.top());
  57. }
  58. /// @brief checks if the given token is an equal operator
  59. void checkTokenEq(const TokenPtr& token) {
  60. ASSERT_TRUE(token);
  61. boost::shared_ptr<TokenEqual> eq =
  62. boost::dynamic_pointer_cast<TokenEqual>(token);
  63. EXPECT_TRUE(eq);
  64. }
  65. /// @brief checks if the given token is an option with the expected code
  66. void checkTokenOption(const TokenPtr& token, uint16_t expected_code) {
  67. ASSERT_TRUE(token);
  68. boost::shared_ptr<TokenOption> opt =
  69. boost::dynamic_pointer_cast<TokenOption>(token);
  70. ASSERT_TRUE(opt);
  71. EXPECT_EQ(expected_code, opt->getCode());
  72. }
  73. /// @brief checks if the given token is a substring operator
  74. void checkTokenSubstring(const TokenPtr& token) {
  75. ASSERT_TRUE(token);
  76. boost::shared_ptr<TokenSubstring> sub =
  77. boost::dynamic_pointer_cast<TokenSubstring>(token);
  78. EXPECT_TRUE(sub);
  79. }
  80. /// @brief checks if the given expression raises the expected message
  81. /// when it is parsed.
  82. void checkError(const string& expr, const string& msg) {
  83. EvalContext eval(universe_);
  84. parsed_ = false;
  85. try {
  86. parsed_ = eval.parseString(expr);
  87. FAIL() << "Expected EvalParseError but nothing was raised";
  88. }
  89. catch (const EvalParseError& ex) {
  90. EXPECT_EQ(msg, ex.what());
  91. EXPECT_FALSE(parsed_);
  92. }
  93. catch (...) {
  94. FAIL() << "Expected EvalParseError but something else was raised";
  95. }
  96. }
  97. /// @brief sets the universe
  98. /// @note the default universe is DHCPv4
  99. void setUniverse(const Option::Universe& universe) {
  100. universe_ = universe;
  101. }
  102. Option::Universe universe_;
  103. bool parsed_; ///< Parsing status
  104. };
  105. // Test the parsing of a basic expression
  106. TEST_F(EvalContextTest, basic) {
  107. EvalContext eval(Option::V4);
  108. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'MSFT'"));
  109. EXPECT_TRUE(parsed_);
  110. }
  111. // Test the parsing of a string terminal
  112. TEST_F(EvalContextTest, string) {
  113. EvalContext eval(Option::V4);
  114. EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'"));
  115. EXPECT_TRUE(parsed_);
  116. ASSERT_EQ(3, eval.expression.size());
  117. TokenPtr tmp1 = eval.expression.at(0);
  118. TokenPtr tmp2 = eval.expression.at(1);
  119. checkTokenString(tmp1, "foo");
  120. checkTokenString(tmp2, "bar");
  121. }
  122. // Test the parsing of a basic expression using integers
  123. TEST_F(EvalContextTest, integer) {
  124. EvalContext eval(Option::V4);
  125. EXPECT_NO_THROW(parsed_ =
  126. eval.parseString("substring(option[123].text, 0, 2) == '42'"));
  127. EXPECT_TRUE(parsed_);
  128. }
  129. // Test the parsing of a hexstring terminal
  130. TEST_F(EvalContextTest, hexstring) {
  131. EvalContext eval(Option::V4);
  132. EXPECT_NO_THROW(parsed_ = eval.parseString("0x666f6f == 'foo'"));
  133. EXPECT_TRUE(parsed_);
  134. ASSERT_EQ(3, eval.expression.size());
  135. TokenPtr tmp = eval.expression.at(0);
  136. checkTokenHexString(tmp, "foo");
  137. }
  138. // Test the parsing of a hexstring terminal with an odd number of
  139. // hexadecimal digits
  140. TEST_F(EvalContextTest, oddHexstring) {
  141. EvalContext eval(Option::V4);
  142. EXPECT_NO_THROW(parsed_ = eval.parseString("0X7 == 'foo'"));
  143. EXPECT_TRUE(parsed_);
  144. ASSERT_EQ(3, eval.expression.size());
  145. TokenPtr tmp = eval.expression.at(0);
  146. checkTokenHexString(tmp, "\a");
  147. }
  148. // Test the parsing of an equal expression
  149. TEST_F(EvalContextTest, equal) {
  150. EvalContext eval(Option::V4);
  151. EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'"));
  152. EXPECT_TRUE(parsed_);
  153. ASSERT_EQ(3, eval.expression.size());
  154. TokenPtr tmp1 = eval.expression.at(0);
  155. TokenPtr tmp2 = eval.expression.at(1);
  156. TokenPtr tmp3 = eval.expression.at(2);
  157. checkTokenString(tmp1, "foo");
  158. checkTokenString(tmp2, "bar");
  159. checkTokenEq(tmp3);
  160. }
  161. // Test the parsing of an option terminal
  162. TEST_F(EvalContextTest, option) {
  163. EvalContext eval(Option::V4);
  164. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'foo'"));
  165. EXPECT_TRUE(parsed_);
  166. ASSERT_EQ(3, eval.expression.size());
  167. checkTokenOption(eval.expression.at(0), 123);
  168. }
  169. // Test parsing of an option identified by name.
  170. TEST_F(EvalContextTest, optionWithName) {
  171. EvalContext eval(Option::V4);
  172. // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
  173. EXPECT_NO_THROW(parsed_ = eval.parseString("option[host-name].text == 'foo'"));
  174. EXPECT_TRUE(parsed_);
  175. ASSERT_EQ(3, eval.expression.size());
  176. checkTokenOption(eval.expression.at(0), 12);
  177. }
  178. // Test checking that whitespace can surround option name.
  179. TEST_F(EvalContextTest, optionWithNameAndWhitespace) {
  180. EvalContext eval(Option::V4);
  181. // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
  182. EXPECT_NO_THROW(parsed_ = eval.parseString("option[ host-name ].text == 'foo'"));
  183. EXPECT_TRUE(parsed_);
  184. ASSERT_EQ(3, eval.expression.size());
  185. checkTokenOption(eval.expression.at(0), 12);
  186. }
  187. // Test checking that newlines can surround option name.
  188. TEST_F(EvalContextTest, optionWithNameAndNewline) {
  189. EvalContext eval(Option::V4);
  190. // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
  191. EXPECT_NO_THROW(parsed_ =
  192. eval.parseString("option[\n host-name \n ].text == \n'foo'"));
  193. EXPECT_TRUE(parsed_);
  194. ASSERT_EQ(3, eval.expression.size());
  195. checkTokenOption(eval.expression.at(0), 12);
  196. }
  197. // Test parsing of an option represented as hexadecimal string.
  198. TEST_F(EvalContextTest, optionHex) {
  199. EvalContext eval(Option::V4);
  200. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].hex == 0x666F6F"));
  201. EXPECT_TRUE(parsed_);
  202. ASSERT_EQ(3, eval.expression.size());
  203. checkTokenOption(eval.expression.at(0), 123);
  204. }
  205. // Test the parsing of a substring expression
  206. TEST_F(EvalContextTest, substring) {
  207. EvalContext eval(Option::V4);
  208. EXPECT_NO_THROW(parsed_ =
  209. eval.parseString("substring('foobar',2,all) == 'obar'"));
  210. EXPECT_TRUE(parsed_);
  211. ASSERT_EQ(6, eval.expression.size());
  212. TokenPtr tmp1 = eval.expression.at(0);
  213. TokenPtr tmp2 = eval.expression.at(1);
  214. TokenPtr tmp3 = eval.expression.at(2);
  215. TokenPtr tmp4 = eval.expression.at(3);
  216. checkTokenString(tmp1, "foobar");
  217. checkTokenString(tmp2, "2");
  218. checkTokenString(tmp3, "all");
  219. checkTokenSubstring(tmp4);
  220. }
  221. // Test some scanner error cases
  222. TEST_F(EvalContextTest, scanErrors) {
  223. checkError("'", "<string>:1.1: Invalid character: '");
  224. checkError("'\''", "<string>:1.3: Invalid character: '");
  225. checkError("'\n'", "<string>:1.1: Invalid character: '");
  226. checkError("0x123h", "<string>:1.6: Invalid character: h");
  227. checkError("=", "<string>:1.1: Invalid character: =");
  228. checkError("subtring", "<string>:1.1: Invalid character: s");
  229. checkError("foo", "<string>:1.1: Invalid character: f");
  230. checkError(" bar", "<string>:1.2: Invalid character: b");
  231. }
  232. // Tests some scanner/parser error cases
  233. TEST_F(EvalContextTest, scanParseErrors) {
  234. checkError("", "<string>:1.1: syntax error, unexpected end of file");
  235. checkError(" ", "<string>:1.2: syntax error, unexpected end of file");
  236. checkError("0x", "<string>:1.1: syntax error, unexpected integer");
  237. checkError("0abc",
  238. "<string>:1.1: syntax error, unexpected integer");
  239. checkError("===", "<string>:1.1-2: syntax error, unexpected ==");
  240. checkError("option[-1].text",
  241. "<string>:1.8-9: Option code has invalid "
  242. "value in -1. Allowed range: 0..255");
  243. checkError("option[256].text",
  244. "<string>:1.8-10: Option code has invalid "
  245. "value in 256. Allowed range: 0..255");
  246. setUniverse(Option::V6);
  247. checkError("option[65536].text",
  248. "<string>:1.8-12: Option code has invalid "
  249. "value in 65536. Allowed range: 0..65535");
  250. setUniverse(Option::V4);
  251. checkError("option[12345678901234567890].text",
  252. "<string>:1.8-27: Failed to convert 12345678901234567890 "
  253. "to an integer.");
  254. checkError("option[123]",
  255. "<string>:1.12: syntax error, unexpected end of file,"
  256. " expecting .");
  257. checkError("option[123].text < 'foo'", "<string>:1.18: Invalid"
  258. " character: <");
  259. checkError("option[-ab].text", "<string>:1.8: Invalid character: -");
  260. checkError("option[0ab].text",
  261. "<string>:1.9-10: syntax error, unexpected option name, "
  262. "expecting ]");
  263. checkError("option[ab_].hex", "<string>:1.8: Invalid character: a");
  264. checkError("option[\nhost-name\n].hex =\n= 'foo'",
  265. "<string>:3.7: Invalid character: =");
  266. checkError("substring('foo',12345678901234567890,1)",
  267. "<string>:1.17-36: Failed to convert 12345678901234567890 "
  268. "to an integer.");
  269. }
  270. // Tests some parser error cases
  271. TEST_F(EvalContextTest, parseErrors) {
  272. checkError("'foo''bar'",
  273. "<string>:1.6-10: syntax error, unexpected constant string, "
  274. "expecting ==");
  275. checkError("== 'ab'", "<string>:1.1-2: syntax error, unexpected ==");
  276. checkError("'foo' ==",
  277. "<string>:1.9: syntax error, unexpected end of file");
  278. checkError("('foo' == 'bar'",
  279. "<string>:1.16: syntax error, unexpected end of file, "
  280. "expecting )");
  281. checkError("('foo' == 'bar') ''",
  282. "<string>:1.18-19: syntax error, unexpected constant string, "
  283. "expecting end of file");
  284. checkError("option 'ab'",
  285. "<string>:1.8-11: syntax error, unexpected "
  286. "constant string, expecting [");
  287. checkError("option(10) == 'ab'",
  288. "<string>:1.7: syntax error, "
  289. "unexpected (, expecting [");
  290. checkError("option['ab'].text == 'foo'",
  291. "<string>:1.8-11: syntax error, "
  292. "unexpected constant string, "
  293. "expecting integer or option name");
  294. checkError("option[ab].text == 'foo'",
  295. "<string>:1.8-9: option 'ab' is not defined");
  296. checkError("option[0xa].text == 'ab'",
  297. "<string>:1.8-10: syntax error, "
  298. "unexpected constant hexstring, "
  299. "expecting integer or option name");
  300. checkError("option[10].bin", "<string>:1.12: Invalid character: b");
  301. checkError("option[boot-size].bin", "<string>:1.19: Invalid character: b");
  302. checkError("substring('foobar') == 'f'",
  303. "<string>:1.19: syntax error, "
  304. "unexpected ), expecting \",\"");
  305. checkError("substring('foobar',3) == 'bar'",
  306. "<string>:1.21: syntax error, unexpected ), expecting \",\"");
  307. checkError("substring('foobar','3',3) == 'bar'",
  308. "<string>:1.20-22: syntax error, unexpected constant string, "
  309. "expecting integer");
  310. checkError("substring('foobar',1,a) == 'foo'",
  311. "<string>:1.22: Invalid character: a");
  312. }
  313. // Tests some type error cases (caught only by the strongly typed parser)
  314. TEST_F(EvalContextTest, typeErrors) {
  315. checkError("'foobar'",
  316. "<string>:1.9: syntax error, unexpected end of file, "
  317. "expecting ==");
  318. checkError("substring('foobar',all,1) == 'foo'",
  319. "<string>:1.20-22: syntax error, unexpected all, "
  320. "expecting integer");
  321. checkError("substring('foobar',0x32,1) == 'foo'",
  322. "<string>:1.20-23: syntax error, unexpected constant "
  323. "hexstring, expecting integer");
  324. checkError("('foo' == 'bar') == 'false'",
  325. "<string>:1.18-19: syntax error, unexpected ==, "
  326. "expecting end of file");
  327. }
  328. };