master_lexer_state_unittest.cc 19.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <dns/master_lexer.h>
16
#include <dns/master_lexer_inputsource.h>
17 18 19 20 21 22 23 24 25 26 27 28 29 30
#include <dns/master_lexer_state.h>

#include <gtest/gtest.h>

#include <sstream>

using namespace isc::dns;
using namespace master_lexer_internal;

namespace {
typedef MasterLexer::Token Token; // shortcut

class MasterLexerStateTest : public ::testing::Test {
protected:
31
    MasterLexerStateTest() : common_options(MasterLexer::INITIAL_WS),
32
                             s_null(NULL),
JINMEI Tatuya's avatar
JINMEI Tatuya committed
33
                             s_crlf(State::getInstance(State::CRLF)),
34
                             s_string(State::getInstance(State::String)),
35
                             s_qstring(State::getInstance(State::QString)),
36
                             options(MasterLexer::NONE),
37
                             orig_options(options)
38
    {}
39

40
    // Specify INITIAL_WS as common initial options.
41 42
    const MasterLexer::Options common_options;
    MasterLexer lexer;
43
    const State* const s_null;
44
    const State& s_crlf;
45
    const State& s_string;
46
    const State& s_qstring;
47
    std::stringstream ss;
48
    MasterLexer::Options options, orig_options;
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
};

// Common check for the end-of-file condition.
// Token is set to END_OF_FILE, and the lexer was NOT last eol state.
// Passed state can be any valid one; they are stateless, just providing the
// interface for inspection.
void
eofCheck(const State& state, MasterLexer& lexer) {
    EXPECT_EQ(Token::END_OF_FILE, state.getToken(lexer).getType());
    EXPECT_FALSE(state.wasLastEOL(lexer));
}

TEST_F(MasterLexerStateTest, startAndEnd) {
    // A simple case: the input is empty, so we begin with start and
    // are immediately done.
64
    lexer.pushSource(ss);
65 66
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    eofCheck(s_crlf, lexer);
67 68 69 70
}

TEST_F(MasterLexerStateTest, startToEOL) {
    ss << "\n";
71 72
    lexer.pushSource(ss);

73 74 75
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
76 77

    // The next lexer session will reach EOF.  Same eof check should pass.
78 79
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    eofCheck(s_crlf, lexer);
80
}
81

JINMEI Tatuya's avatar
JINMEI Tatuya committed
82
TEST_F(MasterLexerStateTest, space) {
83 84 85 86
    // repeat '\t\n' twice (see below), then space after EOL
    ss << " \t\n\t\n ";
    lexer.pushSource(ss);

JINMEI Tatuya's avatar
JINMEI Tatuya committed
87 88 89 90 91
    // by default space characters and tabs will be ignored.  We check this
    // twice; at the second iteration, it's a white space at the beginning
    // of line, but since we don't specify INITIAL_WS option, it's treated as
    // normal space and ignored.
    for (size_t i = 0; i < 2; ++i) {
92 93 94
        EXPECT_EQ(s_null, State::start(lexer, MasterLexer::NONE));
        EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
        EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
JINMEI Tatuya's avatar
JINMEI Tatuya committed
95 96 97 98
    }

    // Now we specify the INITIAL_WS option.  It will be recognized and the
    // corresponding token will be returned.
99 100 101
    EXPECT_EQ(s_null, State::start(lexer, MasterLexer::INITIAL_WS));
    EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
    EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
JINMEI Tatuya's avatar
JINMEI Tatuya committed
102 103
}

104
TEST_F(MasterLexerStateTest, parentheses) {
105
    ss << "\n(\na\n )\n "; // 1st \n is to check if 'was EOL' is set to false
106
    lexer.pushSource(ss);
107

108
    EXPECT_EQ(s_null, State::start(lexer, common_options)); // handle \n
109

110
    // Now handle '('.  It skips \n and recognize 'a' as string
111 112 113 114
    EXPECT_EQ(0, s_crlf.getParenCount(lexer)); // check pre condition
    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(1, s_crlf.getParenCount(lexer)); // check post condition
    EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
115

116
    // skip 'a'
117
    s_string.handle(lexer);
118 119 120 121

    // Then handle ')'.  '\n' before ')' isn't recognized because
    // it's canceled due to the '('.  Likewise, the space after the '\n'
    // shouldn't be recognized but should be just ignored.
122 123
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    EXPECT_EQ(0, s_crlf.getParenCount(lexer));
124 125 126

    // Now, temporarily disabled options are restored: Both EOL and the
    // initial WS are recognized
127 128 129
    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
130 131
}

132 133
TEST_F(MasterLexerStateTest, nestedParentheses) {
    // This is an unusual, but allowed (in this implementation) case.
134
    ss << "(a(b)\n c)\n ";
135 136
    lexer.pushSource(ss);

137 138 139 140 141
    EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '('
    s_string.handle(lexer);                      // consume 'a'
    EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '('
    s_string.handle(lexer);                     // consume 'b'
    EXPECT_EQ(2, s_crlf.getParenCount(lexer)); // now the count is 2
142 143

    // Close the inner most parentheses.  count will be decreased, but option
144 145
    // shouldn't be restored yet, so the intermediate EOL or initial WS won't
    // be recognized.
146 147 148
    EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume ')'
    s_string.handle(lexer);                      // consume 'c'
    EXPECT_EQ(1, s_crlf.getParenCount(lexer));
149 150 151

    // Close the outermost parentheses.  count will be reset to 0, and original
    // options are restored.
152
    EXPECT_EQ(s_null, State::start(lexer, common_options));
153 154 155

    // Now, temporarily disabled options are restored: Both EOL and the
    // initial WS are recognized
156 157 158
    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
159 160
}

161 162 163 164
TEST_F(MasterLexerStateTest, unbalancedParentheses) {
    // Only closing paren is provided.  We prepend a \n to check if it's
    // correctly canceled after detecting the error.
    ss << "\n)";
165 166
    ss << "(a";
    lexer.pushSource(ss);
167

168 169
    EXPECT_EQ(s_null, State::start(lexer, common_options)); // consume '\n'
    EXPECT_TRUE(s_crlf.wasLastEOL(lexer)); // this \n was remembered
170 171 172

    // Now checking ')'.  The result should be error, count shouldn't be
    // changed.  "last EOL" should be canceled.
173 174 175 176 177 178
    EXPECT_EQ(0, s_crlf.getParenCount(lexer));
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    EXPECT_EQ(0, s_crlf.getParenCount(lexer));
    ASSERT_EQ(Token::ERROR, s_crlf.getToken(lexer).getType());
    EXPECT_EQ(Token::UNBALANCED_PAREN, s_crlf.getToken(lexer).getErrorCode());
    EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
179

180
    // Reach EOF with a dangling open parenthesis.
181 182 183 184 185 186 187
    EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '('
    s_string.handle(lexer);                      // consume 'a'
    EXPECT_EQ(1, s_crlf.getParenCount(lexer));
    EXPECT_EQ(s_null, State::start(lexer, common_options));    // reach EOF
    ASSERT_EQ(Token::ERROR, s_crlf.getToken(lexer).getType());
    EXPECT_EQ(Token::UNBALANCED_PAREN, s_crlf.getToken(lexer).getErrorCode());
    EXPECT_EQ(0, s_crlf.getParenCount(lexer)); // should be reset to 0
188 189 190 191 192 193 194
}

TEST_F(MasterLexerStateTest, startToComment) {
    // Begin with 'start', skip space, then encounter a comment.  Skip
    // the rest of the line, and recognize the new line.  Note that the
    // second ';' is simply ignored.
    ss << "  ;a;\n";
195 196 197 198
    ss << ";a;";           // Likewise, but the comment ends with EOF.
    lexer.pushSource(ss);

    // Comment ending with EOL
199 200
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
201

202
    // Comment ending with EOF
203 204
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
205 206
}

207 208 209 210 211
TEST_F(MasterLexerStateTest, commentAfterParen) {
    // comment after an opening parenthesis.  The code that is tested by
    // other tests should also ensure that it works correctly, but we
    // check it explicitly.
    ss << "( ;this is a comment\na)\n";
212 213
    lexer.pushSource(ss);

214
    // consume '(', skip comments, consume 'a', then consume ')'
215 216 217 218
    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    s_string.handle(lexer);
    EXPECT_EQ(s_null, State::start(lexer, common_options));
    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
219 220
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
221
TEST_F(MasterLexerStateTest, crlf) {
222 223 224 225 226 227 228
    ss << "\r\n";               // case 1
    ss << "\r ";                // case 2
    ss << "\r;comment\na";      // case 3
    ss << "\r";                 // case 4
    lexer.pushSource(ss);

    // 1. A sequence of \r, \n is recognized as a single 'end-of-line'
229 230 231 232
    EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
    EXPECT_EQ(s_null, s_crlf.handle(lexer));   // recognize '\n'
    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
    EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
JINMEI Tatuya's avatar
JINMEI Tatuya committed
233

234 235
    // 2. Single '\r' (not followed by \n) is recognized as a single
    // 'end-of-line'.  then there will be "initial WS"
236
    EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
237
    // see ' ', "unget" it
238 239 240
    EXPECT_EQ(s_null, s_crlf.handle(lexer));
    EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize ' '
    EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
JINMEI Tatuya's avatar
JINMEI Tatuya committed
241

242
    // 3. comment between \r and \n
243
    EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
244
    // skip comments, recognize '\n'
245 246 247
    EXPECT_EQ(s_null, s_crlf.handle(lexer));
    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
    EXPECT_EQ(&s_string, State::start(lexer, common_options));
248
    EXPECT_EQ(s_null, s_string.handle(lexer)); // skip 'a'
JINMEI Tatuya's avatar
JINMEI Tatuya committed
249

250
    // 4. \r then EOF
251
    EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
252
    // see EOF, then "unget" it
253 254 255
    EXPECT_EQ(s_null, s_crlf.handle(lexer));
    EXPECT_EQ(s_null, State::start(lexer, common_options));  // recognize EOF
    EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
JINMEI Tatuya's avatar
JINMEI Tatuya committed
256 257
}

258 259
// Commonly used check for string related test cases, checking if the given
// token has expected values.
260
void
261 262
stringTokenCheck(const std::string& expected, const MasterLexer::Token& token,
                 bool quoted = false)
263
{
264
    EXPECT_EQ(quoted ? Token::QSTRING : Token::STRING, token.getType());
265 266 267 268 269 270 271
    EXPECT_EQ(expected, token.getString());
    const std::string actual(token.getStringRegion().beg,
                             token.getStringRegion().beg +
                             token.getStringRegion().len);
    EXPECT_EQ(expected, actual);
}

272
TEST_F(MasterLexerStateTest, string) {
273 274 275 276 277 278 279 280
    // Check with simple strings followed by separate characters
    ss << "followed-by-EOL\n";
    ss << "followed-by-CR\r";
    ss << "followed-by-space ";
    ss << "followed-by-tab\t";
    ss << "followed-by-comment;this is comment and ignored\n";
    ss << "followed-by-paren(closing)";
    ss << "followed-by-EOF";
281 282 283
    lexer.pushSource(ss);

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
284
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see \n
285
    EXPECT_FALSE(s_string.wasLastEOL(lexer));
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
    stringTokenCheck("followed-by-EOL", s_string.getToken(lexer));
    EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see \r
    stringTokenCheck("followed-by-CR", s_string.getToken(lexer));
    EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // handle \r...
    EXPECT_EQ(s_null, s_crlf.handle(lexer)); // ...and skip it

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see ' '
    stringTokenCheck("followed-by-space", s_string.getToken(lexer));

    // skip ' ', then recognize the next string
    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see \t
    stringTokenCheck("followed-by-tab", s_string.getToken(lexer));

    // skip \t, then recognize the next string
    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see comment
    stringTokenCheck("followed-by-comment", s_string.getToken(lexer));
    EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see '('
    stringTokenCheck("followed-by-paren", s_string.getToken(lexer));
    EXPECT_EQ(&s_string, State::start(lexer, common_options)); // str in ()
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize the str, see ')'
    stringTokenCheck("closing", s_string.getToken(lexer));

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see EOF
    stringTokenCheck("followed-by-EOF", s_string.getToken(lexer));
320 321
}

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
TEST_F(MasterLexerStateTest, stringEscape) {
    // some of the separate characters should be considered part of the
    // string if escaped.
    ss << "escaped\\ space ";
    ss << "escaped\\\ttab ";
    ss << "escaped\\(paren ";
    ss << "escaped\\)close ";
    ss << "escaped\\;comment ";
    ss << "escaped\\\\ backslash "; // second '\' shouldn't escape ' '
    lexer.pushSource(ss);

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see ' ' at end
    stringTokenCheck("escaped\\ space", s_string.getToken(lexer));

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see ' ' at end
    stringTokenCheck("escaped\\\ttab", s_string.getToken(lexer));

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see ' ' at end
    stringTokenCheck("escaped\\(paren", s_string.getToken(lexer));

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see ' ' at end
    stringTokenCheck("escaped\\)close", s_string.getToken(lexer));

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see ' ' at end
    stringTokenCheck("escaped\\;comment", s_string.getToken(lexer));

    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see ' ' in mid
    stringTokenCheck("escaped\\\\", s_string.getToken(lexer));
356 357 358 359 360

    // Confirm the word that follows the escaped '\' is correctly recognized.
    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see ' ' at end
    stringTokenCheck("backslash", s_string.getToken(lexer));
361 362
}

363 364
TEST_F(MasterLexerStateTest, quotedString) {
    ss << "\"ignore-quotes\"\n";
365 366 367 368 369 370
    ss << "\"quoted string\" "; // space is part of the qstring
    // also check other separator characters. note that \r doesn't cause
    // UNBALANCED_QUOTES.  Not sure if it's intentional, but that's how the
    // BIND 9 version works, so we follow it (it should be too minor to matter
    // in practice anyway)
    ss << "\"quoted()\t\rstring\" ";
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
    ss << "\"escape\\ in quote\" ";
    ss << "\"escaped\\\"\" ";
    ss << "\"escaped backslash\\\\\" ";
    ss << "\"no;comment\"";
    lexer.pushSource(ss);

    // by default, '"' doesn't have any special meaning and part of string
    EXPECT_EQ(&s_string, State::start(lexer, common_options));
    EXPECT_EQ(s_null, s_string.handle(lexer)); // recognize str, see \n
    stringTokenCheck("\"ignore-quotes\"", s_string.getToken(lexer));
    EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
    EXPECT_TRUE(s_string.wasLastEOL(lexer));

    // If QSTRING is specified in option, '"' is regarded as a beginning of
    // a quoted string.
    const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_FALSE(s_string.wasLastEOL(lexer)); // EOL is canceled due to '"'
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    stringTokenCheck("quoted string", s_string.getToken(lexer), true);

392 393 394 395 396
    // Also checks other separator characters within a qstring
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    stringTokenCheck("quoted()\t\rstring", s_string.getToken(lexer), true);

397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
    // escape character mostly doesn't have any effect in the qstring
    // processing
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    stringTokenCheck("escape\\ in quote", s_string.getToken(lexer), true);

    // The only exception is the quotation mark itself.  Note that the escape
    // only works on the quotation mark immediately after it.
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    stringTokenCheck("escaped\"", s_string.getToken(lexer), true);

    // quoted '\' then '"'.  Unlike the previous case '"' shouldn't be
    // escaped.
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    stringTokenCheck("escaped backslash\\\\", s_string.getToken(lexer), true);

    // ';' has no meaning in a quoted string (not indicating a comment)
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    stringTokenCheck("no;comment", s_string.getToken(lexer), true);
}

TEST_F(MasterLexerStateTest, brokenQuotedString) {
    ss << "\"unbalanced-quote\n";
    ss << "\"quoted\\\n\" ";
    ss << "\"unclosed quote and EOF";
    lexer.pushSource(ss);

    // EOL is encountered without closing the quote
    const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
    EXPECT_EQ(Token::UNBALANCED_QUOTES,
              s_qstring.getToken(lexer).getErrorCode());
    // We can resume after the error from the '\n'
    EXPECT_EQ(s_null, State::start(lexer, options));
    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());

    // \n is okay in a quoted string if escaped
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    stringTokenCheck("quoted\\\n", s_string.getToken(lexer), true);

    // EOF is encountered without closing the quote
    EXPECT_EQ(&s_qstring, State::start(lexer, options));
    EXPECT_EQ(s_null, s_qstring.handle(lexer));
    ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
    EXPECT_EQ(Token::UNEXPECTED_END, s_qstring.getToken(lexer).getErrorCode());
    // If we continue we'll simply see the EOF
    EXPECT_EQ(s_null, State::start(lexer, options));
    EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
}

453
}