diff options
author | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-08 18:49:07 +0000 |
---|---|---|
committer | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-08 18:49:07 +0000 |
commit | f6562c1c8b7867b48622b82ad44c84ba50599011 (patch) | |
tree | f1d542d7a55532122304171b4f64e4efe4cd493a /base | |
parent | 8b25cef5d60a45bfc2b6b7292d46812cd5d835c5 (diff) | |
download | chromium_src-f6562c1c8b7867b48622b82ad44c84ba50599011.zip chromium_src-f6562c1c8b7867b48622b82ad44c84ba50599011.tar.gz chromium_src-f6562c1c8b7867b48622b82ad44c84ba50599011.tar.bz2 |
[Test Expectations] Implement the low-level parser.
This parser will be used by a higher-level class (not yet written) that will act
as the Parser::Delegate.
BUG=173176
Review URL: https://chromiumcodereview.appspot.com/12096119
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@181530 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/base.gyp | 3 | ||||
-rw-r--r-- | base/test/expectations/parser.cc | 201 | ||||
-rw-r--r-- | base/test/expectations/parser.h | 143 | ||||
-rw-r--r-- | base/test/expectations/parser_unittest.cc | 209 |
4 files changed, 556 insertions, 0 deletions
diff --git a/base/base.gyp b/base/base.gyp index ca3e0e4..4d77598 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -546,6 +546,7 @@ 'task_runner_util_unittest.cc', 'template_util_unittest.cc', 'test/expectations/expectation_unittest.cc', + 'test/expectations/parser_unittest.cc', 'test/sequenced_worker_pool_owner.cc', 'test/sequenced_worker_pool_owner.h', 'test/trace_event_analyzer_unittest.cc', @@ -787,6 +788,8 @@ 'perftimer.cc', 'test/expectations/expectation.cc', 'test/expectations/expectation.h', + 'test/expectations/parser.cc', + 'test/expectations/parser.h', 'test/main_hook.cc', 'test/main_hook.h', 'test/main_hook_ios.mm', diff --git a/base/test/expectations/parser.cc b/base/test/expectations/parser.cc new file mode 100644 index 0000000..05678a5 --- /dev/null +++ b/base/test/expectations/parser.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/expectations/parser.h" + +#include "base/string_util.h" + +namespace test_expectations { + +Parser::Parser(Delegate* delegate, const std::string& input) + : delegate_(delegate), + input_(input), + pos_(NULL), + end_(NULL), + line_number_(0), + data_error_(false) { +} + +Parser::~Parser() { +} + +void Parser::Parse() { + pos_ = &input_[0]; + end_ = pos_ + input_.length(); + + line_number_ = 1; + + StateFuncPtr state = &Parser::Start; + while (state) { + state = (this->*state)(); + } +} + +inline bool Parser::HasNext() { + return pos_ < end_; +} + +Parser::StateFunc Parser::Start() { + // If at the start of a line is whitespace, skip it and arrange to come back + // here. + if (IsAsciiWhitespace(*pos_)) + return SkipWhitespaceAndNewLines(&Parser::Start); + + // Handle comments at the start of lines. + if (*pos_ == '#') + return &Parser::ParseComment; + + // After arranging to come back here from skipping whitespace and comments, + // the parser may be at the end of the input. + if (pos_ >= end_) + return NULL; + + current_ = Expectation(); + data_error_ = false; + + return &Parser::ParseBugURL; +} + +Parser::StateFunc Parser::ParseComment() { + if (*pos_ != '#') + return SyntaxError("Invalid start of comment"); + + do { + ++pos_; + } while (HasNext() && *pos_ != '\n'); + + return &Parser::Start; +} + +Parser::StateFunc Parser::ParseBugURL() { + return SkipWhitespace(ExtractString( + &Parser::BeginModifiers)); +} + +Parser::StateFunc Parser::BeginModifiers() { + if (*pos_ != '[' || !HasNext()) + return SyntaxError("Expected '[' for start of modifiers"); + + ++pos_; + return SkipWhitespace(&Parser::InModifiers); +} + +Parser::StateFunc Parser::InModifiers() { + if (*pos_ == ']') + return &Parser::EndModifiers; + + return ExtractString(SkipWhitespace( + &Parser::SaveModifier)); +} + +Parser::StateFunc Parser::SaveModifier() { + if (extracted_string_.empty()) + return SyntaxError("Invalid modifier list"); + + Configuration config; + if (ConfigurationFromString(extracted_string_, &config)) { + if (current_.configuration != CONFIGURATION_UNSPECIFIED) + DataError("Cannot use more than one configuration modifier"); + else + current_.configuration = config; + } else { + Platform platform; + if (PlatformFromString(extracted_string_, &platform)) + current_.platforms.push_back(platform); + else + DataError("Invalid modifier string"); + } + + return SkipWhitespace(&Parser::InModifiers); +} + +Parser::StateFunc Parser::EndModifiers() { + if (*pos_ != ']' || !HasNext()) + return SyntaxError("Expected ']' for end of modifiers list"); + + ++pos_; + return SkipWhitespace(&Parser::ParseTestName); +} + +Parser::StateFunc Parser::ParseTestName() { + return ExtractString(&Parser::SaveTestName); +} + +Parser::StateFunc Parser::SaveTestName() { + if (extracted_string_.empty()) + return SyntaxError("Invalid test name"); + + current_.test_name = extracted_string_.as_string(); + return SkipWhitespace(&Parser::ParseExpectation); +} + +Parser::StateFunc Parser::ParseExpectation() { + if (*pos_ != '=' || !HasNext()) + return SyntaxError("Expected '=' for expectation result"); + + ++pos_; + return SkipWhitespace(&Parser::ParseExpectationType); +} + +Parser::StateFunc Parser::ParseExpectationType() { + return ExtractString(&Parser::SaveExpectationType); +} + +Parser::StateFunc Parser::SaveExpectationType() { + if (!ResultFromString(extracted_string_, ¤t_.result)) + DataError("Unknown expectation type"); + + return SkipWhitespace(&Parser::End); +} + +Parser::StateFunc Parser::End() { + if (!data_error_) + delegate_->EmitExpectation(current_); + + if (HasNext()) + return SkipWhitespaceAndNewLines(&Parser::Start); + + return NULL; +} + +Parser::StateFunc Parser::ExtractString(StateFunc success) { + const char* start = pos_; + while (!IsAsciiWhitespace(*pos_) && *pos_ != ']' && HasNext()) { + ++pos_; + if (*pos_ == '#') { + return SyntaxError("Unexpected start of comment"); + } + } + extracted_string_ = base::StringPiece(start, pos_ - start); + return success; +} + +Parser::StateFunc Parser::SkipWhitespace(Parser::StateFunc next) { + while ((*pos_ == ' ' || *pos_ == '\t') && HasNext()) { + ++pos_; + } + return next; +} + +Parser::StateFunc Parser::SkipWhitespaceAndNewLines(Parser::StateFunc next) { + while (IsAsciiWhitespace(*pos_) && HasNext()) { + if (*pos_ == '\n') { + ++line_number_; + } + ++pos_; + } + return next; +} + +Parser::StateFunc Parser::SyntaxError(const std::string& message) { + delegate_->OnSyntaxError(message); + return NULL; +} + +void Parser::DataError(const std::string& error) { + data_error_ = true; + delegate_->OnDataError(error); +} + +} // namespace test_expectations diff --git a/base/test/expectations/parser.h b/base/test/expectations/parser.h new file mode 100644 index 0000000..365943c --- /dev/null +++ b/base/test/expectations/parser.h @@ -0,0 +1,143 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_EXPECTATIONS_PARSER_H_ +#define BASE_TEST_EXPECTATIONS_PARSER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/string_piece.h" +#include "base/test/expectations/expectation.h" + +namespace test_expectations { + +// This is the internal parser for test expectations. It parses an input +// string and reports information to its Delegate as it's processing the +// input. +// +// The input format is documented here: +// https://docs.google.com/a/chromium.org/document/d/1edhMJ5doY_dzfbKNCzeJJ-8XxPrexTbNL2Y_jVvLB8Q/view +// +// Basic format: +// "http://bug/1234 [ OS-Version ] Test.Name = Result" +// +// The parser is implemented as a state machine, with each state returning a +// function pointer to the next state. +class Parser { + public: + // The parser will call these methods on its delegate during a Parse() + // operation. + class Delegate { + public: + // When a well-formed and valid Expectation has been parsed from the input, + // it is reported to the delegate via this method. + virtual void EmitExpectation(const Expectation& expectation) = 0; + + // Called when the input string is not well-formed. Parsing will stop after + // this method is called. + virtual void OnSyntaxError(const std::string& message) = 0; + + // Called when an Expectation has been parsed because it is well-formed but + // contains invalid data (i.e. the modifiers or result are not valid + // keywords). This Expectation will not be reported via EmitExpectation. + virtual void OnDataError(const std::string& message) = 0; + }; + + // Creates a new parser for |input| that will send data to |delegate|. + Parser(Delegate* delegate, const std::string& input); + ~Parser(); + + // Runs the parser of the input string. + void Parse(); + + private: + // This bit of hackery is used to implement a function pointer type that + // returns a pointer to a function of the same signature. Since a definition + // like that is inherently recursive, it's impossible to do: + // type StateFunc(*StateFunc)(StateData*); + // However, this approach works without the need to use void*. Inspired by + // <http://www.gotw.ca/gotw/057.htm>. + struct StateFunc; + typedef StateFunc(Parser::*StateFuncPtr)(); + struct StateFunc { + StateFunc(StateFuncPtr pf) : pf_(pf) {} + operator StateFuncPtr() { + return pf_; + } + StateFuncPtr pf_; + }; + + // Tests whether there is at least one more character at pos_ before end_. + bool HasNext(); + + // The parser state functions. On entry, the parser state is at the beginning + // of the token. Each returns a function pointer to the next state function, + // or NULL to end parsing. On return, the parser is at the beginning of the + // next token. + StateFunc Start(); + StateFunc ParseComment(); + StateFunc ParseBugURL(); + StateFunc BeginModifiers(); + StateFunc InModifiers(); + StateFunc SaveModifier(); + StateFunc EndModifiers(); + StateFunc ParseTestName(); + StateFunc SaveTestName(); + StateFunc ParseExpectation(); + StateFunc ParseExpectationType(); + StateFunc SaveExpectationType(); + StateFunc End(); + + // A state function that collects character data from the current position + // to the next whitespace character. Returns the |success| function when at + // the end of the string, with the data stored in |extracted_string_|. + StateFunc ExtractString(StateFunc success); + + // Function that skips over horizontal whitespace characters and then returns + // the |next| state. + StateFunc SkipWhitespace(StateFunc next); + + // Does the same as SkipWhitespace but includes newlines. + StateFunc SkipWhitespaceAndNewLines(StateFunc next); + + // State function that reports the given syntax error |message| to the + // delegate and then returns NULL, ending the parse loop. + StateFunc SyntaxError(const std::string& message); + + // Function that reports the data |error| to the delegate without stopping + // parsing. + void DataError(const std::string& error); + + // Parser delegate. + Delegate* delegate_; + + // The input string. + std::string input_; + + // Current location in the |input_|. + const char* pos_; + + // Pointer to the end of the |input_|. + const char* end_; + + // Current line number, as updated by SkipWhitespace(). + int line_number_; + + // The character data extracted from |input_| as a result of the + // ExtractString() state. + base::StringPiece extracted_string_; + + // The Expectation object that is currently being processed by the parser. + // Reset in Start(). + Expectation current_; + + // If DataError() has been called during the course of parsing |current_|. + // If true, then |current_| will not be emitted to the Delegate. + bool data_error_; +}; + +} // namespace test_expectations + +#endif // BASE_TEST_EXPECTATIONS_PARSER_H_ diff --git a/base/test/expectations/parser_unittest.cc b/base/test/expectations/parser_unittest.cc new file mode 100644 index 0000000..1c55a05 --- /dev/null +++ b/base/test/expectations/parser_unittest.cc @@ -0,0 +1,209 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/expectations/parser.h" + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "testing/gtest/include/gtest/gtest.h" + +using test_expectations::Parser; + +class TestExpectationParserTest : public testing::Test, + public Parser::Delegate { + public: + virtual void EmitExpectation( + const test_expectations::Expectation& expectation) OVERRIDE { + expectations_.push_back(expectation); + } + + virtual void OnSyntaxError(const std::string& message) OVERRIDE { + syntax_error_ = message; + } + + virtual void OnDataError(const std::string& error) OVERRIDE { + data_errors_.push_back(error); + } + + protected: + std::vector<test_expectations::Expectation> expectations_; + std::string syntax_error_; + std::vector<std::string> data_errors_; +}; + +TEST_F(TestExpectationParserTest, Basic) { + Parser(this, + "http://crbug.com/1234 [ Win-8 ] DouglasTest.PoopsOk = Timeout"). + Parse(); + EXPECT_TRUE(syntax_error_.empty()); + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(1u, expectations_.size()); + EXPECT_EQ("DouglasTest.PoopsOk", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_TIMEOUT, expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[0].configuration); + + ASSERT_EQ(1u, expectations_[0].platforms.size()); + EXPECT_EQ("Win", expectations_[0].platforms[0].name); + EXPECT_EQ("8", expectations_[0].platforms[0].variant); +} + +TEST_F(TestExpectationParserTest, MultiModifier) { + Parser(this, "BUG [ Win-XP Mac ] OhMy.MeOhMy = Failure").Parse(); + EXPECT_TRUE(syntax_error_.empty()); + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(1u, expectations_.size()); + EXPECT_EQ("OhMy.MeOhMy", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_FAILURE, + expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[0].configuration); + + ASSERT_EQ(2u, expectations_[0].platforms.size()); + + EXPECT_EQ("Win", expectations_[0].platforms[0].name); + EXPECT_EQ("XP", expectations_[0].platforms[0].variant); + + EXPECT_EQ("Mac", expectations_[0].platforms[1].name); + EXPECT_EQ("", expectations_[0].platforms[1].variant); +} + +TEST_F(TestExpectationParserTest, EmptyModifier) { + Parser(this, + "BUG [] First.Test = Failure\n" + "BUG2 [ ] Second.Test = Crash").Parse(); + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(2u, expectations_.size()); + + EXPECT_EQ("First.Test", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_FAILURE, + expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[0].configuration); + EXPECT_EQ(0u, expectations_[0].platforms.size()); + + EXPECT_EQ("Second.Test", expectations_[1].test_name); + EXPECT_EQ(test_expectations::RESULT_CRASH, + expectations_[1].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[1].configuration); + EXPECT_EQ(0u, expectations_[1].platforms.size()); +} + +TEST_F(TestExpectationParserTest, MultiLine) { + Parser(this, + "BUG [ Linux ] Line.First = Failure\n" + "\n" + "# A test comment.\n" + "BUG2 [ Release ] Line.Second = Skip").Parse(); + EXPECT_TRUE(syntax_error_.empty()); + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(2u, expectations_.size()); + EXPECT_EQ("Line.First", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_FAILURE, expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[0].configuration); + + ASSERT_EQ(1u, expectations_[0].platforms.size()); + EXPECT_EQ("Linux", expectations_[0].platforms[0].name); + EXPECT_EQ("", expectations_[0].platforms[0].variant); + + EXPECT_EQ("Line.Second", expectations_[1].test_name); + EXPECT_EQ(test_expectations::RESULT_SKIP, expectations_[1].result); + EXPECT_EQ(test_expectations::CONFIGURATION_RELEASE, + expectations_[1].configuration); + EXPECT_EQ(0u, expectations_[1].platforms.size()); +} + +TEST_F(TestExpectationParserTest, MultiLineWithComments) { + Parser(this, + " # Comment for your thoughts\n" + " \t \n" + "BUG [ Mac-10.8 Debug] Foo=Bar =Skip # Why not another comment?\n" + "BUG2 [Win-XP\tWin-Vista ] Cow.GoesMoo =\tTimeout\n\n").Parse(); + EXPECT_TRUE(syntax_error_.empty()) << syntax_error_; + EXPECT_EQ(0u, data_errors_.size()); + + ASSERT_EQ(2u, expectations_.size()); + EXPECT_EQ("Foo=Bar", expectations_[0].test_name); + EXPECT_EQ(test_expectations::RESULT_SKIP, expectations_[0].result); + EXPECT_EQ(test_expectations::CONFIGURATION_DEBUG, + expectations_[0].configuration); + + ASSERT_EQ(1u, expectations_[0].platforms.size()); + EXPECT_EQ("Mac", expectations_[0].platforms[0].name); + EXPECT_EQ("10.8", expectations_[0].platforms[0].variant); + + EXPECT_EQ("Cow.GoesMoo", expectations_[1].test_name); + EXPECT_EQ(test_expectations::RESULT_TIMEOUT, expectations_[1].result); + EXPECT_EQ(test_expectations::CONFIGURATION_UNSPECIFIED, + expectations_[1].configuration); + + ASSERT_EQ(2u, expectations_[1].platforms.size()); + EXPECT_EQ("Win", expectations_[1].platforms[0].name); + EXPECT_EQ("XP", expectations_[1].platforms[0].variant); + EXPECT_EQ("Win", expectations_[1].platforms[0].name); + EXPECT_EQ("Vista", expectations_[1].platforms[1].variant); +} + +TEST_F(TestExpectationParserTest, WeirdSpaces) { + Parser(this, " BUG [Linux] Weird = Skip ").Parse(); + EXPECT_EQ(1u, expectations_.size()); + EXPECT_TRUE(syntax_error_.empty()); + EXPECT_EQ(0u, data_errors_.size()); +} + +TEST_F(TestExpectationParserTest, SyntaxErrors) { + const char* kErrors[] = { + "Foo [ dfasd", + "Foo [Linux] # This is an illegal comment", + "Foo [Linux] Bar # Another illegal comment.", + "Foo [Linux] Bar = # Another illegal comment.", + "Foo[Linux]Bar=Failure", + "Foo\n[Linux] Bar = Failure", + "Foo [\nLinux] Bar = Failure", + "Foo [Linux\n] Bar = Failure", + "Foo [ Linux ] \n Bar = Failure", + "Foo [ Linux ] Bar =\nFailure", + "Foo [ Linux \n ] Bar =\nFailure", + }; + + for (size_t i = 0; i < arraysize(kErrors); ++i) { + Parser(this, kErrors[i]).Parse(); + EXPECT_FALSE(syntax_error_.empty()) + << "Should have error for #" << i << ": " << kErrors[i]; + syntax_error_.clear(); + } +} + +TEST_F(TestExpectationParserTest, DataErrors) { + const char* kOneError[] = { + "http://crbug.com/1234 [MagicBrowzR] BadModifier = Timeout", + "________ [Linux] BadResult = WhatNow", + "http://wkb.ug/1234 [Debug Release Win-7] MultipleConfigs = Skip", + }; + + for (size_t i = 0; i < arraysize(kOneError); ++i) { + Parser(this, kOneError[i]).Parse(); + EXPECT_EQ(1u, data_errors_.size()) << kOneError[i]; + data_errors_.clear(); + } + + const char* kTwoErrors[] = { + ". [Mac-TurningIntoiOS] BadModifierVariant.BadResult = Foobar", + "1234 [ Debug Release OS/2 ] MultipleConfigs.BadModifier = Pass", + }; + + for (size_t i = 0; i < arraysize(kTwoErrors); ++i) { + Parser(this, kTwoErrors[i]).Parse(); + EXPECT_EQ(2u, data_errors_.size()) << kTwoErrors[i]; + data_errors_.clear(); + } +} |