blob: 1b18212a7422dd998c726ab4fa59bb832bb5ae37 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <hidl-util/StringHelper.h>
#include <string>
#include <vector>
#include "../Lint.h"
#include "../LintRegistry.h"
#include "Coordinator.h"
using ::testing::Contains;
using ::testing::ContainsRegex;
using ::testing::Property;
namespace android {
class HidlLintTest : public ::testing::Test {
protected:
Coordinator coordinator;
void SetUp() override {
char* argv[2] = {const_cast<char*>("hidl-lint"),
const_cast<char*>("-rlint_test:system/tools/hidl/lint/test/interfaces")};
coordinator.parseOptions(2, argv, "", [](int /* opt */, char* /* optarg */) {});
}
void getLintsForHal(const std::string& name, std::vector<Lint>* errors) {
std::vector<FQName> targets;
FQName fqName;
if (!FQName::parse(name, &fqName)) {
FAIL() << "Could not parse fqName: " << name;
}
if (fqName.isFullyQualified()) {
targets.push_back(fqName);
} else {
status_t err = coordinator.appendPackageInterfacesToVector(fqName, &targets);
if (err != OK) {
FAIL() << "Could not get sources for: " << name;
}
}
for (const FQName& fqName : targets) {
AST* ast = coordinator.parse(fqName);
if (ast == nullptr) {
FAIL() << "Could not parse " << fqName.name() << ". Aborting.";
}
LintRegistry::get()->runAllLintFunctions(*ast, errors);
}
}
};
#define EXPECT_NO_LINT(interface) \
do { \
std::vector<Lint> errors; \
getLintsForHal(interface, &errors); \
EXPECT_EQ(0, errors.size()); \
} while (false)
#define EXPECT_LINT(interface, errorMsg) \
do { \
std::vector<Lint> errors; \
getLintsForHal(interface, &errors); \
EXPECT_EQ(1, errors.size()); \
if (errors.size() != 1) break; \
EXPECT_THAT(errors[0].getMessage(), ContainsRegex(errorMsg)); \
} while (false)
#define EXPECT_A_LINT(interface, errorMsg) \
do { \
std::vector<Lint> errors; \
getLintsForHal(interface, &errors); \
EXPECT_LE(1, errors.size()); \
if (errors.size() < 1) break; \
EXPECT_THAT(errors, Contains(Property(&Lint::getMessage, ContainsRegex(errorMsg)))); \
} while (false)
TEST_F(HidlLintTest, OnewayLintTest) {
// Has no errors (empty). Lint size should be 0.
EXPECT_NO_LINT("lint_test.oneway@1.0::IEmpty");
// Only has either oneway or nononeway methods. Lint size should be 0.
EXPECT_NO_LINT("lint_test.oneway@1.0::IOneway");
EXPECT_NO_LINT("lint_test.oneway@1.0::INonOneway");
// A child of a mixed interface should not trigger a lint if it is oneway/nononeway.
// Lint size should be 0
EXPECT_NO_LINT("lint_test.oneway@1.0::IMixedOnewayChild");
EXPECT_NO_LINT("lint_test.oneway@1.0::IMixedNonOnewayChild");
// A child with the same oneway type should not trigger a lint. Lint size should be 0.
EXPECT_NO_LINT("lint_test.oneway@1.0::IOnewayChild");
EXPECT_NO_LINT("lint_test.oneway@1.0::INonOnewayChild");
// This interface is mixed. Should have a lint.
EXPECT_LINT("lint_test.oneway@1.0::IMixed", "IMixed has both oneway and non-oneway methods.");
// Regardless of parent, if interface is mixed, it should have a lint.
EXPECT_LINT("lint_test.oneway@1.0::IMixedMixedChild",
"IMixedMixedChild has both oneway and non-oneway methods.");
// When onewaytype is different from parent it should trigger a lint.
EXPECT_LINT("lint_test.oneway@1.0::IOnewayOpposite",
"IOnewayOpposite should only have oneway methods");
EXPECT_LINT("lint_test.oneway@1.0::INonOnewayOpposite",
"INonOnewayOpposite should only have non-oneway methods");
}
TEST_F(HidlLintTest, SafeunionLintTest) {
// Has no errors (empty). Even though types.hal has a lint.
EXPECT_NO_LINT("lint_test.safeunion@1.0::IEmpty");
// A child of an interface that refers to a union should not lint unless it refers to a union
EXPECT_NO_LINT("lint_test.safeunion@1.1::IReference");
// Should lint the union type definition
EXPECT_LINT("lint_test.safeunion@1.0::types", "union InTypes.*defined");
EXPECT_LINT("lint_test.safeunion@1.0::IDefined", "union SomeUnion.*defined");
// Should mention that a union type is being referenced and where that type is.
EXPECT_LINT("lint_test.safeunion@1.0::IReference", "Reference to union type.*types.hal");
// Referencing a union inside a struct should lint
EXPECT_LINT("lint_test.safeunion@1.1::types", "Reference to union type.*1\\.0/types.hal");
// Defining a union inside a struct should lint
EXPECT_LINT("lint_test.safeunion@1.0::IUnionInStruct", "union SomeUnionInStruct.*defined");
// Reference to a struct that contains a union should lint
EXPECT_LINT("lint_test.safeunion@1.1::IReferStructWithUnion",
"Reference to struct.*contains a union type.");
}
TEST_F(HidlLintTest, ImportTypesTest) {
// Imports types.hal file from package
EXPECT_LINT("lint_test.import_types@1.0::IImport", "Redundant import");
// Imports types.hal from other package
EXPECT_LINT("lint_test.import_types@1.0::IImportOther", "This imports every type");
// Imports types.hal from previous version of the same package
EXPECT_LINT("lint_test.import_types@1.1::types", "This imports every type");
// Imports types.hal from same package with fully qualified name
EXPECT_LINT("lint_test.import_types@1.1::IImport", "Redundant import");
}
TEST_F(HidlLintTest, SmallStructsTest) {
// Referencing bad structs should not lint
EXPECT_NO_LINT("lint_test.small_structs@1.0::IReference");
// Empty structs/unions should lint
EXPECT_LINT("lint_test.small_structs@1.0::IEmptyStruct", "contains no elements");
EXPECT_A_LINT("lint_test.small_structs@1.0::IEmptyUnion", "contains no elements");
// Structs/unions with single field should lint
EXPECT_LINT("lint_test.small_structs@1.0::ISingleStruct", "only contains 1 element");
EXPECT_A_LINT("lint_test.small_structs@1.0::ISingleUnion", "only contains 1 element");
}
TEST_F(HidlLintTest, DocCommentRefTest) {
EXPECT_NO_LINT("lint_test.doc_comments@1.0::ICorrect");
// Should lint since nothing follows the keyword
EXPECT_LINT("lint_test.doc_comments@1.0::INoReturn",
"should be followed by a return parameter");
EXPECT_LINT("lint_test.doc_comments@1.0::INoParam", "should be followed by a parameter name");
EXPECT_LINT("lint_test.doc_comments@1.0::IReturnSpace",
"should be followed by a return parameter");
// Typos should be caught
EXPECT_LINT("lint_test.doc_comments@1.0::IWrongReturn", "is not a return parameter");
EXPECT_LINT("lint_test.doc_comments@1.0::IWrongParam", "is not an argument");
// Incorrectly marked as @param should lint as a param
EXPECT_LINT("lint_test.doc_comments@1.0::ISwitched", "is not an argument");
// Incorrectly marked as @param should lint as a param
EXPECT_LINT("lint_test.doc_comments@1.0::IParamAfterReturn",
"@param references should come before @return");
// Reversed order should be caught
EXPECT_LINT("lint_test.doc_comments@1.0::IRevReturn",
"@return references should be ordered the same way they show up");
EXPECT_LINT("lint_test.doc_comments@1.0::IRevParam",
"@param references should be ordered the same way they show up");
// Referencing the same param/return multiple times should be caught
EXPECT_LINT("lint_test.doc_comments@1.0::IDoubleReturn", "was referenced multiple times");
EXPECT_LINT("lint_test.doc_comments@1.0::IDoubleParam", "was referenced multiple times");
}
TEST_F(HidlLintTest, MethodVersionsTest) {
// Extends baseMethod correctly
EXPECT_NO_LINT("lint_test.method_versions@1.0::IChangeBase");
// Extends IBase.foo through @1.0::IChangeBase correctly
EXPECT_NO_LINT("lint_test.method_versions@1.1::IChangeBase");
// Lints because lintBadName_V1_x is not minor_major version naming
EXPECT_LINT("lint_test.method_versions@1.0::IBase",
"Methods should follow the camelCase naming convention.");
// Lints because incorrect package name
EXPECT_LINT("lint_test.method_versions@1.0::IChild", "interface is in package version 1.0");
// Lints because wrong minor version
EXPECT_LINT("lint_test.method_versions@1.0::IWrongMinor",
"Methods should follow the camelCase naming convention.");
// Lints because underscore in wrong place
EXPECT_LINT("lint_test.method_versions@1.0::IWrongUnderscore",
"when defining a new version of a method");
// Method does not exist in any of the super types
EXPECT_LINT("lint_test.method_versions@1.1::IMethodDNE", "Could not find method");
// Methods are not in camel case
EXPECT_LINT("lint_test.method_versions@1.0::IPascalCase",
"Methods should follow the camelCase naming convention.");
EXPECT_LINT("lint_test.method_versions@1.0::IHybrid",
"Methods should follow the camelCase naming convention.");
EXPECT_LINT("lint_test.method_versions@1.0::ISnakeCase",
"Methods should follow the camelCase naming convention.");
}
TEST_F(HidlLintTest, EnumMaxAllTest) {
// Implements MAX correctly
EXPECT_NO_LINT("lint_test.enum_max_all@1.0::IFoo");
// Lint since MAX and ALL are enum values
EXPECT_LINT("lint_test.enum_max_all@1.0::IMax",
"\"MAX\" enum values have been known to become out of date");
EXPECT_LINT("lint_test.enum_max_all@1.0::IAll",
"\"ALL\" enum values have been known to become out of date");
EXPECT_LINT("lint_test.enum_max_all@1.0::ICount",
"\"COUNT\" enum values have been known to become out of date");
// Lint since MAX and ALL are parts of the enum values
EXPECT_LINT("lint_test.enum_max_all@1.0::IMax2",
"\"MAX\" enum values have been known to become out of date");
EXPECT_LINT("lint_test.enum_max_all@1.0::IAll2",
"\"ALL\" enum values have been known to become out of date");
EXPECT_LINT("lint_test.enum_max_all@1.0::ICount2",
"\"COUNT\" enum values have been known to become out of date");
}
TEST_F(HidlLintTest, UnhandledDocCommentTest) {
EXPECT_LINT("lint_test.unhandled_comments@1.0::types",
"cannot be processed since it is in an unrecognized place");
// Even single line comments are unhandled
EXPECT_LINT("lint_test.unhandled_comments@1.0::ISingleComment",
"cannot be processed since it is in an unrecognized place");
}
TEST_F(HidlLintTest, NamingConventionsTest) {
EXPECT_LINT("lint_test.naming_conventions@1.0::IBad_Interface",
"type .* should be named .* PascalCase");
EXPECT_LINT("lint_test.naming_conventions@1.0::IBadStruct",
"type .* should be named .* PascalCase");
EXPECT_LINT("lint_test.naming_conventions@1.0::IBadEnum",
"type .* should be named .* PascalCase");
EXPECT_A_LINT("lint_test.naming_conventions@1.0::IBadUnion",
"type .* should be named .* PascalCase");
EXPECT_LINT("lint_test.naming_conventions@1.0::IBadStructMember",
"member .* of type .* should be named .* camelCase");
EXPECT_A_LINT("lint_test.naming_conventions@1.0::IBadUnionMember",
"member .* of type .* should be named .* camelCase");
EXPECT_LINT("lint_test.naming_conventions@1.0::IBadEnumValue",
"enumeration .* of enum .* should be named .* UPPER_SNAKE_CASE");
}
} // namespace android