context_unittest.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  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 checks if the given token is a string with the expected value
  30. void checkTokenString(const TokenPtr& token, const std::string& expected) {
  31. ASSERT_TRUE(token);
  32. boost::shared_ptr<TokenString> str =
  33. boost::dynamic_pointer_cast<TokenString>(token);
  34. ASSERT_TRUE(str);
  35. Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
  36. ValueStack values;
  37. EXPECT_NO_THROW(token->evaluate(*pkt4, values));
  38. ASSERT_EQ(1, values.size());
  39. EXPECT_EQ(expected, values.top());
  40. }
  41. /// @brief checks if the given token is a hex string with the expected value
  42. void checkTokenHexString(const TokenPtr& token,
  43. const std::string& expected) {
  44. ASSERT_TRUE(token);
  45. boost::shared_ptr<TokenHexString> hex =
  46. boost::dynamic_pointer_cast<TokenHexString>(token);
  47. ASSERT_TRUE(hex);
  48. Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
  49. ValueStack values;
  50. EXPECT_NO_THROW(token->evaluate(*pkt4, values));
  51. ASSERT_EQ(1, values.size());
  52. EXPECT_EQ(expected, values.top());
  53. }
  54. /// @brief checks if the given token is an equal operator
  55. void checkTokenEq(const TokenPtr& token) {
  56. ASSERT_TRUE(token);
  57. boost::shared_ptr<TokenEqual> eq =
  58. boost::dynamic_pointer_cast<TokenEqual>(token);
  59. EXPECT_TRUE(eq);
  60. }
  61. /// @brief checks if the given token is an option with the expected code
  62. void checkTokenOption(const TokenPtr& token, uint16_t expected_code) {
  63. ASSERT_TRUE(token);
  64. boost::shared_ptr<TokenOption> opt =
  65. boost::dynamic_pointer_cast<TokenOption>(token);
  66. ASSERT_TRUE(opt);
  67. EXPECT_EQ(expected_code, opt->getCode());
  68. }
  69. /// @brief checks if the given token is a substring operator
  70. void checkTokenSubstring(const TokenPtr& token) {
  71. ASSERT_TRUE(token);
  72. boost::shared_ptr<TokenSubstring> sub =
  73. boost::dynamic_pointer_cast<TokenSubstring>(token);
  74. EXPECT_TRUE(sub);
  75. }
  76. /// @brief checks if the given expression raises the expected message
  77. /// when it is parsed.
  78. void checkError(const string& expr, const string& msg) {
  79. EvalContext eval(Option::V4);
  80. parsed_ = false;
  81. try {
  82. parsed_ = eval.parseString(expr);
  83. FAIL() << "Expected EvalParseError but nothing was raised";
  84. }
  85. catch (const EvalParseError& ex) {
  86. EXPECT_EQ(msg, ex.what());
  87. EXPECT_FALSE(parsed_);
  88. }
  89. catch (...) {
  90. FAIL() << "Expected EvalParseError but something else was raised";
  91. }
  92. }
  93. bool parsed_; ///< Parsing status
  94. };
  95. // Test the parsing of a basic expression
  96. TEST_F(EvalContextTest, basic) {
  97. EvalContext eval(Option::V4);
  98. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'MSFT'"));
  99. EXPECT_TRUE(parsed_);
  100. }
  101. // Test the parsing of a string terminal
  102. TEST_F(EvalContextTest, string) {
  103. EvalContext eval(Option::V4);
  104. EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'"));
  105. EXPECT_TRUE(parsed_);
  106. ASSERT_EQ(3, eval.expression.size());
  107. TokenPtr tmp1 = eval.expression.at(0);
  108. TokenPtr tmp2 = eval.expression.at(1);
  109. checkTokenString(tmp1, "foo");
  110. checkTokenString(tmp2, "bar");
  111. }
  112. // Test the parsing of a basic expression using integers
  113. TEST_F(EvalContextTest, integer) {
  114. EvalContext eval(Option::V4);
  115. EXPECT_NO_THROW(parsed_ =
  116. eval.parseString("substring(option[123].text, 0, 2) == '42'"));
  117. EXPECT_TRUE(parsed_);
  118. }
  119. // Test the parsing of a hexstring terminal
  120. TEST_F(EvalContextTest, hexstring) {
  121. EvalContext eval(Option::V4);
  122. EXPECT_NO_THROW(parsed_ = eval.parseString("0x666f6f == 'foo'"));
  123. EXPECT_TRUE(parsed_);
  124. ASSERT_EQ(3, eval.expression.size());
  125. TokenPtr tmp = eval.expression.at(0);
  126. checkTokenHexString(tmp, "foo");
  127. }
  128. // Test the parsing of a hexstring terminal with an odd number of
  129. // hexadecimal digits
  130. TEST_F(EvalContextTest, oddHexstring) {
  131. EvalContext eval(Option::V4);
  132. EXPECT_NO_THROW(parsed_ = eval.parseString("0X7 == 'foo'"));
  133. EXPECT_TRUE(parsed_);
  134. ASSERT_EQ(3, eval.expression.size());
  135. TokenPtr tmp = eval.expression.at(0);
  136. checkTokenHexString(tmp, "\a");
  137. }
  138. // Test the parsing of an equal expression
  139. TEST_F(EvalContextTest, equal) {
  140. EvalContext eval(Option::V4);
  141. EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'"));
  142. EXPECT_TRUE(parsed_);
  143. ASSERT_EQ(3, eval.expression.size());
  144. TokenPtr tmp1 = eval.expression.at(0);
  145. TokenPtr tmp2 = eval.expression.at(1);
  146. TokenPtr tmp3 = eval.expression.at(2);
  147. checkTokenString(tmp1, "foo");
  148. checkTokenString(tmp2, "bar");
  149. checkTokenEq(tmp3);
  150. }
  151. // Test the parsing of an option terminal
  152. TEST_F(EvalContextTest, option) {
  153. EvalContext eval(Option::V4);
  154. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'foo'"));
  155. EXPECT_TRUE(parsed_);
  156. ASSERT_EQ(3, eval.expression.size());
  157. checkTokenOption(eval.expression.at(0), 123);
  158. }
  159. // Test parsing of an option identified by name.
  160. TEST_F(EvalContextTest, optionWithName) {
  161. EvalContext eval(Option::V4);
  162. // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
  163. EXPECT_NO_THROW(parsed_ = eval.parseString("option[host-name].text == 'foo'"));
  164. EXPECT_TRUE(parsed_);
  165. ASSERT_EQ(3, eval.expression.size());
  166. checkTokenOption(eval.expression.at(0), 12);
  167. }
  168. // Test checking that whitespace can surround option name.
  169. TEST_F(EvalContextTest, optionWithNameAndWhitespace) {
  170. EvalContext eval(Option::V4);
  171. // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
  172. EXPECT_NO_THROW(parsed_ = eval.parseString("option[ host-name ].text == 'foo'"));
  173. EXPECT_TRUE(parsed_);
  174. ASSERT_EQ(3, eval.expression.size());
  175. checkTokenOption(eval.expression.at(0), 12);
  176. }
  177. // Test parsing of an option represented as hexadecimal string.
  178. TEST_F(EvalContextTest, optionHex) {
  179. EvalContext eval(Option::V4);
  180. EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].hex == 0x666F6F"));
  181. EXPECT_TRUE(parsed_);
  182. ASSERT_EQ(3, eval.expression.size());
  183. checkTokenOption(eval.expression.at(0), 123);
  184. }
  185. // Test the parsing of a substring expression
  186. TEST_F(EvalContextTest, substring) {
  187. EvalContext eval(Option::V4);
  188. EXPECT_NO_THROW(parsed_ =
  189. eval.parseString("substring('foobar',2,all) == 'obar'"));
  190. EXPECT_TRUE(parsed_);
  191. ASSERT_EQ(6, eval.expression.size());
  192. TokenPtr tmp1 = eval.expression.at(0);
  193. TokenPtr tmp2 = eval.expression.at(1);
  194. TokenPtr tmp3 = eval.expression.at(2);
  195. TokenPtr tmp4 = eval.expression.at(3);
  196. checkTokenString(tmp1, "foobar");
  197. checkTokenString(tmp2, "2");
  198. checkTokenString(tmp3, "all");
  199. checkTokenSubstring(tmp4);
  200. }
  201. // Test some scanner error cases
  202. TEST_F(EvalContextTest, scanErrors) {
  203. checkError("'", "<string>:1.1: Invalid character: '");
  204. checkError("'\''", "<string>:1.3: Invalid character: '");
  205. checkError("'\n'", "<string>:1.1: Invalid character: '");
  206. checkError("0x123h", "<string>:1.6: Invalid character: h");
  207. checkError("=", "<string>:1.1: Invalid character: =");
  208. checkError("subtring", "<string>:1.1: Invalid character: s");
  209. checkError("foo", "<string>:1.1: Invalid character: f");
  210. checkError(" bar", "<string>:1.2: Invalid character: b");
  211. }
  212. // Tests some scanner/parser error cases
  213. TEST_F(EvalContextTest, scanParseErrors) {
  214. checkError("", "<string>:1.1: syntax error, unexpected end of file");
  215. checkError(" ", "<string>:1.2: syntax error, unexpected end of file");
  216. checkError("0x", "<string>:1.1: syntax error, unexpected integer");
  217. checkError("0abc",
  218. "<string>:1.1: syntax error, unexpected integer");
  219. checkError("===", "<string>:1.1-2: syntax error, unexpected ==");
  220. checkError("option[-1].text",
  221. "<string>:1.8-9: Option code has invalid "
  222. "value in -1. Allowed range: 0..65535");
  223. checkError("option[65536].text",
  224. "<string>:1.8-12: Option code has invalid "
  225. "value in 65536. Allowed range: 0..65535");
  226. checkError("option[12345678901234567890].text",
  227. "<string>:1.8-27: Failed to convert 12345678901234567890 "
  228. "to an integer.");
  229. checkError("option[123]",
  230. "<string>:1.12: syntax error, unexpected end of file,"
  231. " expecting .");
  232. checkError("option[123].text < 'foo'", "<string>:1.18: Invalid"
  233. " character: <");
  234. checkError("option[-ab].text", "<string>:1.8: Invalid character: -");
  235. checkError("option[0ab].text",
  236. "<string>:1.9-10: syntax error, unexpected option name, "
  237. "expecting ]");
  238. checkError("option[ab_].hex", "<string>:1.8: Invalid character: a");
  239. checkError("substring('foo',12345678901234567890,1)",
  240. "<string>:1.17-36: Failed to convert 12345678901234567890 "
  241. "to an integer.");
  242. }
  243. // Tests some parser error cases
  244. TEST_F(EvalContextTest, parseErrors) {
  245. checkError("'foo''bar'",
  246. "<string>:1.6-10: syntax error, unexpected constant string, "
  247. "expecting ==");
  248. checkError("== 'ab'", "<string>:1.1-2: syntax error, unexpected ==");
  249. checkError("'foo' ==",
  250. "<string>:1.9: syntax error, unexpected end of file");
  251. checkError("option 'ab'",
  252. "<string>:1.8-11: syntax error, unexpected "
  253. "constant string, expecting [");
  254. checkError("option(10) == 'ab'",
  255. "<string>:1.7: syntax error, "
  256. "unexpected (, expecting [");
  257. checkError("option['ab'].text == 'foo'",
  258. "<string>:1.8-11: syntax error, "
  259. "unexpected constant string, "
  260. "expecting integer or option name");
  261. checkError("option[ab].text == 'foo'",
  262. "<string>:1.8-9: option 'ab' is not defined");
  263. checkError("option[0xa].text == 'ab'",
  264. "<string>:1.8-10: syntax error, "
  265. "unexpected constant hexstring, "
  266. "expecting integer or option name");
  267. checkError("substring('foobar') == 'f'",
  268. "<string>:1.19: syntax error, "
  269. "unexpected ), expecting \",\"");
  270. checkError("substring('foobar',3) == 'bar'",
  271. "<string>:1.21: syntax error, unexpected ), expecting \",\"");
  272. checkError("substring('foobar','3',3) == 'bar'",
  273. "<string>:1.20-22: syntax error, unexpected constant string, "
  274. "expecting integer");
  275. checkError("substring('foobar',1,a) == 'foo'",
  276. "<string>:1.22: Invalid character: a");
  277. }
  278. // Tests some type error cases (caught only by the strongly typed parser)
  279. TEST_F(EvalContextTest, typeErrors) {
  280. checkError("'foobar'",
  281. "<string>:1.9: syntax error, unexpected end of file, "
  282. "expecting ==");
  283. checkError("substring('foobar',all,1) == 'foo'",
  284. "<string>:1.20-22: syntax error, unexpected all, "
  285. "expecting integer");
  286. checkError("substring('foobar',0x32,1) == 'foo'",
  287. "<string>:1.20-23: syntax error, unexpected constant "
  288. "hexstring, expecting integer");
  289. }
  290. };