blob: 611124943d6863e05de8428e172ac7462ac80711 [file] [log] [blame]
// Copyright 2017-2020 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Utility functions for parser testing.
#ifndef VERIBLE_COMMON_PARSER_PARSER_TEST_UTIL_H_
#define VERIBLE_COMMON_PARSER_PARSER_TEST_UTIL_H_
#include <string> // for string
#include <vector>
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "common/analysis/matcher/descent_path.h"
#include "common/text/parser_verifier.h"
#include "common/text/symbol.h"
#include "common/text/token_info.h"
#include "common/text/token_info_test_util.h"
namespace verible {
// Tests the parser on source text that is valid input.
// class AnalyzerType is any class with a absl::Status AnalyzerType::Analyze()
// method.
template <class AnalyzerType>
void TestParserAcceptValid(const std::string& code, int i) {
VLOG(1) << "test_data[" << i << "] = '" << code << "'\n";
AnalyzerType analyzer(code, "<<inline-test>>");
absl::Status status = analyzer.Analyze();
if (!status.ok()) {
// Print more detailed error message.
const auto& rejected_tokens = analyzer.GetRejectedTokens();
if (!rejected_tokens.empty()) {
EXPECT_TRUE(status.ok())
<< "Rejected valid code:\n"
<< code << "\nRejected token: " << rejected_tokens[0].token_info;
} else {
EXPECT_TRUE(status.ok()) << "Rejected valid code:\n" << code;
}
}
EXPECT_TRUE(analyzer.SyntaxTree().get()) << "Missing tree on code:\n" << code;
}
// Tests the parser on source text that is invalid input.
// class AnalyzerType is any class with a absl::Status AnalyzerType::Analyze()
// method.
template <class AnalyzerType>
void TestParserRejectInvalid(const TokenInfoTestData& test, int i) {
VLOG(1) << "test_data[" << i << "] = '" << test.code << "'\n";
ASSERT_FALSE(test.expected_tokens.empty());
// Find the first not-don't-care token.
int iteration = 0;
do {
AnalyzerType analyzer(test.code, "<<inline-test>>"); // copies code
const absl::Status status = analyzer.Analyze();
EXPECT_FALSE(status.ok())
<< "Accepted invalid code (iteration: " << iteration << "):\n"
<< test.code;
const auto& rejected_tokens = analyzer.GetRejectedTokens();
ASSERT_FALSE(rejected_tokens.empty());
const absl::string_view base_text = analyzer.Data().Contents();
const auto expected_error_tokens = test.FindImportantTokens(base_text);
ASSERT_FALSE(expected_error_tokens.empty());
// Only check the first rejected token, ignore the rest.
const auto& expected_error_token = expected_error_tokens.front();
EXPECT_EQ(expected_error_token, rejected_tokens[0].token_info);
++iteration;
// Run the analyzer a second time to make sure the parser cleared
// away any state, and finds the same error.
} while (iteration < 2);
}
struct ErrorRecoveryTestCase {
// Code containing a syntax error.
std::string code;
// Node path that is expected to exist due to error-recovery.
// TODO(b/64093049): generalize to use AST matcher classes.
matcher::DescentPath tree_path;
};
template <class AnalyzerType>
void TestParserErrorRecovered(const ErrorRecoveryTestCase& test, int i) {
VLOG(1) << "test_data[" << i << "] = '" << test.code << "'\n";
int iteration = 0;
do {
AnalyzerType analyzer(test.code, "<<inline-test>>");
absl::Status status = analyzer.Analyze();
EXPECT_FALSE(status.ok())
<< "Accepted invalid code (iteration: " << iteration << "):\n"
<< test.code;
const auto rejected_tokens = analyzer.GetRejectedTokens();
EXPECT_FALSE(rejected_tokens.empty());
// Only check the first rejected token, ignore the rest.
const auto& tree = analyzer.SyntaxTree();
const auto matching_paths = matcher::GetAllDescendantsFromPath(
*ABSL_DIE_IF_NULL(tree), test.tree_path);
EXPECT_FALSE(matching_paths.empty())
<< "Expected tree path not found. code:\n"
<< test.code;
++iteration;
// Run the analyzer a second time to make sure the parser cleared
// away any state, and produces the same result.
} while (iteration < 2);
}
template <class AnalyzerType>
void TestParserAllMatched(const std::string& code, int i) {
VLOG(1) << "test_data[" << i << "] = '" << code << "'\n";
AnalyzerType analyzer(code, "<<inline-test>>");
absl::Status status = analyzer.Analyze();
EXPECT_TRUE(status.ok());
const Symbol* tree_ptr = analyzer.SyntaxTree().get();
EXPECT_NE(tree_ptr, nullptr) << "Missing syntax tree with input:\n" << code;
if (tree_ptr == nullptr) return; // Already failed, abort this test case.
const Symbol& root = *tree_ptr;
ParserVerifier verifier(root, analyzer.Data().GetTokenStreamView());
const auto unmatched = verifier.Verify();
EXPECT_EQ(unmatched.size(), 0)
<< "On code:\n"
<< code << "\nFirst unmatched token: " << unmatched.front();
}
} // namespace verible
#endif // VERIBLE_COMMON_PARSER_PARSER_TEST_UTIL_H_