Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added unit tests and fixed unequal braces bug. #87

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 280 additions & 0 deletions #config_parser_test.cc#
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
#include "gtest/gtest.h"
#include "config_parser.h"

TEST(NginxConfigParserTest, SimpleConfig) {
NginxConfigParser parser;
NginxConfig out_config;

bool success = parser.Parse("example_config", &out_config);

EXPECT_TRUE(success);
}

class NginxParserTest : public ::testing::Test {
protected:
NginxConfigParser parser;
NginxConfig output;
bool parse(const std::string input){
std::stringstream straem(input);
return(parser.Parse(&straem, &output));
}
};

TEST_F(NginxParserTest, UnequalBraces){
EXPECT_TRUE(parse("server {listen 80;}"));
EXPECT_FALSE(parse("server {listen 80;"));
//FIXED:
//The above passes, but should not. Is a bug to fix.
}

TEST_F(NginxParserTest, EmptyInput){
EXPECT_TRUE(parse(""));
}

TEST(NginxParserTest, ToString){
NginxConfigStatement s;
s.tokens_.push_back("foo");
s.tokens_.push_back("bar");
//EXPECT_EQ(s.ToString(0), "foo bar;\n");
//The above doesn't pass, but should. (TODO).
}

// An nginx config file parser.
//
// See:
// http://wiki.nginx.org/Configuration
// http://blog.martinfjordvald.com/2010/07/nginx-primer/
//
// How Nginx does it:
// http://lxr.nginx.org/source/src/core/ngx_conf_file.c

#include <cstdio>
#include <fstream>
#include <iostream>
#include <memory>
#include <stack>
#include <string>
#include <vector>

#include "config_parser.h"

std::string NginxConfig::ToString(int depth) {
std::string serialized_config;
for (const auto& statement : statements_) {
serialized_config.append(statement->ToString(depth));
}
return serialized_config;
}

std::string NginxConfigStatement::ToString(int depth) {
std::string serialized_statement;
for (int i = 0; i < depth; ++i) {
serialized_statement.append(" ");
}
for (unsigned int i = 0; i < tokens_.size(); ++i) {
if (i != 0) {
serialized_statement.append(" ");
}
serialized_statement.append(tokens_[i]);
}
if (child_block_.get() != nullptr) {
serialized_statement.append(" {\n");
serialized_statement.append(child_block_->ToString(depth + 1));
for (int i = 0; i < depth; ++i) {
serialized_statement.append(" ");
}
serialized_statement.append("}");
} else {
serialized_statement.append(";");
}
serialized_statement.append("\n");
return serialized_statement;
}

const char* NginxConfigParser::TokenTypeAsString(TokenType type) {
switch (type) {
case TOKEN_TYPE_START: return "TOKEN_TYPE_START";
case TOKEN_TYPE_NORMAL: return "TOKEN_TYPE_NORMAL";
case TOKEN_TYPE_START_BLOCK: return "TOKEN_TYPE_START_BLOCK";
case TOKEN_TYPE_END_BLOCK: return "TOKEN_TYPE_END_BLOCK";
case TOKEN_TYPE_COMMENT: return "TOKEN_TYPE_COMMENT";
case TOKEN_TYPE_STATEMENT_END: return "TOKEN_TYPE_STATEMENT_END";
case TOKEN_TYPE_EOF: return "TOKEN_TYPE_EOF";
case TOKEN_TYPE_ERROR: return "TOKEN_TYPE_ERROR";
default: return "Unknown token type";
}
}

NginxConfigParser::TokenType NginxConfigParser::ParseToken(std::istream* input,
std::string* value) {
TokenParserState state = TOKEN_STATE_INITIAL_WHITESPACE;
while (input->good()) {
const char c = input->get();
if (!input->good()) {
break;
}
switch (state) {
case TOKEN_STATE_INITIAL_WHITESPACE:
switch (c) {
case '{':
*value = c;
return TOKEN_TYPE_START_BLOCK;
case '}':
*value = c;
return TOKEN_TYPE_END_BLOCK;
case '#':
*value = c;
state = TOKEN_STATE_TOKEN_TYPE_COMMENT;
continue;
case '"':
*value = c;
state = TOKEN_STATE_DOUBLE_QUOTE;
continue;
case '\'':
*value = c;
state = TOKEN_STATE_SINGLE_QUOTE;
continue;
case ';':
*value = c;
return TOKEN_TYPE_STATEMENT_END;
case ' ':
case '\t':
case '\n':
case '\r':
continue;
default:
*value += c;
state = TOKEN_STATE_TOKEN_TYPE_NORMAL;
continue;
}
case TOKEN_STATE_SINGLE_QUOTE:
// TODO: the end of a quoted token should be followed by whitespace.
// TODO: Maybe also define a QUOTED_STRING token type.
// TODO: Allow for backslash-escaping within strings.
*value += c;
if (c == '\'') {
return TOKEN_TYPE_NORMAL;
}
continue;
case TOKEN_STATE_DOUBLE_QUOTE:
*value += c;
if (c == '"') {
return TOKEN_TYPE_NORMAL;
}
continue;
case TOKEN_STATE_TOKEN_TYPE_COMMENT:
if (c == '\n' || c == '\r') {
return TOKEN_TYPE_COMMENT;
}
*value += c;
continue;
case TOKEN_STATE_TOKEN_TYPE_NORMAL:
if (c == ' ' || c == '\t' || c == '\n' || c == '\t' ||
c == ';' || c == '{' || c == '}') {
input->unget();
return TOKEN_TYPE_NORMAL;
}
*value += c;
continue;
}
}

// If we get here, we reached the end of the file.
if (state == TOKEN_STATE_SINGLE_QUOTE ||
state == TOKEN_STATE_DOUBLE_QUOTE) {
return TOKEN_TYPE_ERROR;
}

return TOKEN_TYPE_EOF;
}

bool NginxConfigParser::Parse(std::istream* config_file, NginxConfig* config) {
std::stack<NginxConfig*> config_stack;
config_stack.push(config);
TokenType last_token_type = TOKEN_TYPE_START;
TokenType token_type;
while (true) {
std::string token;
token_type = ParseToken(config_file, &token);
printf ("%s: %s\n", TokenTypeAsString(token_type), token.c_str());
if (token_type == TOKEN_TYPE_ERROR) {
break;
}

if (token_type == TOKEN_TYPE_COMMENT) {
// Skip comments.
continue;
}

if (token_type == TOKEN_TYPE_START) {
// Error.
break;
} else if (token_type == TOKEN_TYPE_NORMAL) {
if (last_token_type == TOKEN_TYPE_START ||
last_token_type == TOKEN_TYPE_STATEMENT_END ||
last_token_type == TOKEN_TYPE_START_BLOCK ||
last_token_type == TOKEN_TYPE_END_BLOCK ||
last_token_type == TOKEN_TYPE_NORMAL) {
if (last_token_type != TOKEN_TYPE_NORMAL) {
config_stack.top()->statements_.emplace_back(
new NginxConfigStatement);
}
config_stack.top()->statements_.back().get()->tokens_.push_back(
token);
} else {
// Error.
break;
}
} else if (token_type == TOKEN_TYPE_STATEMENT_END) {
if (last_token_type != TOKEN_TYPE_NORMAL) {
// Error.
break;
}
} else if (token_type == TOKEN_TYPE_START_BLOCK) {
if (last_token_type != TOKEN_TYPE_NORMAL) {
// Error.
break;
}
NginxConfig* const new_config = new NginxConfig;
config_stack.top()->statements_.back().get()->child_block_.reset(
new_config);
config_stack.push(new_config);
} else if (token_type == TOKEN_TYPE_END_BLOCK) {
if (last_token_type != TOKEN_TYPE_STATEMENT_END) {
// Error.
break;
}
config_stack.pop();
} else if (token_type == TOKEN_TYPE_EOF) {
if (last_token_type != TOKEN_TYPE_STATEMENT_END &&
last_token_type != TOKEN_TYPE_END_BLOCK) {
// Error.
break;
}
return true;
} else {
// Error. Unknown token.
break;
}
last_token_type = token_type;
}

printf ("Bad transition from %s to %s\n",
TokenTypeAsString(last_token_type),
TokenTypeAsString(token_type));
return false;
}

bool NginxConfigParser::Parse(const char* file_name, NginxConfig* config) {
std::ifstream config_file;
config_file.open(file_name);
if (!config_file.good()) {
printf ("Failed to open config file: %s\n", file_name);
return false;
}

const bool return_value =
Parse(dynamic_cast<std::istream*>(&config_file), config);
config_file.close();
return return_value;
}
Binary file added config_parser
Binary file not shown.
11 changes: 9 additions & 2 deletions config_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,18 @@ bool NginxConfigParser::Parse(std::istream* config_file, NginxConfig* config) {
config_stack.pop();
} else if (token_type == TOKEN_TYPE_EOF) {
if (last_token_type != TOKEN_TYPE_STATEMENT_END &&
last_token_type != TOKEN_TYPE_END_BLOCK) {
last_token_type != TOKEN_TYPE_END_BLOCK &&
last_token_type != TOKEN_TYPE_START) {//for empty config
// Error.
break;
}
return true;
//Check that the config_stack does not have leftover tokens after parsing
//e.g. a config that has a '{' without the matching '}'
if (config_stack.size() == 1) {
return true;
} else {
break;
}
} else {
// Error. Unknown token.
break;
Expand Down
Binary file added config_parser_test
Binary file not shown.
36 changes: 36 additions & 0 deletions config_parser_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,39 @@ TEST(NginxConfigParserTest, SimpleConfig) {

EXPECT_TRUE(success);
}

//Fixture
class NginxParserTest : public ::testing::Test {
protected:
NginxConfigParser parser;
NginxConfig output;
bool parse(const std::string input){
std::stringstream straem(input);
return(parser.Parse(&straem, &output));
}
};

TEST_F(NginxParserTest, BadConfig){
EXPECT_FALSE(parse("server { listen 80 } "));
}

TEST_F(NginxParserTest, UnequalBraces){
EXPECT_TRUE(parse("server {listen 80;}"));
EXPECT_FALSE(parse("server {listen 80;"));
//FIXED:
//The above passes, but should not. Is a bug to fix.
}

TEST_F(NginxParserTest, EmptyInput){
EXPECT_TRUE(parse(""));
//Empty Input should be valid
}

//TODO:
//TEST(NginxParserTest, ToString){
// NginxConfigStatement s;
// s.tokens_.push_back("foo");
// s.tokens_.push_back("bar");
//EXPECT_EQ(s.ToString(0), "foo bar;\n");
//The above doesn't pass, but should. (TODO).
//}
Binary file added gtest-all.o
Binary file not shown.
Binary file added libgtest.a
Binary file not shown.