summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
authorrsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-08 18:49:07 +0000
committerrsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-08 18:49:07 +0000
commitf6562c1c8b7867b48622b82ad44c84ba50599011 (patch)
treef1d542d7a55532122304171b4f64e4efe4cd493a /base
parent8b25cef5d60a45bfc2b6b7292d46812cd5d835c5 (diff)
downloadchromium_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.gyp3
-rw-r--r--base/test/expectations/parser.cc201
-rw-r--r--base/test/expectations/parser.h143
-rw-r--r--base/test/expectations/parser_unittest.cc209
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_, &current_.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();
+ }
+}