summaryrefslogtreecommitdiff
path: root/mojo/public/cpp/bindings/tests/validation_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'mojo/public/cpp/bindings/tests/validation_unittest.cc')
-rw-r--r--mojo/public/cpp/bindings/tests/validation_unittest.cc498
1 files changed, 498 insertions, 0 deletions
diff --git a/mojo/public/cpp/bindings/tests/validation_unittest.cc b/mojo/public/cpp/bindings/tests/validation_unittest.cc
new file mode 100644
index 0000000000..7af7396d4e
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/validation_unittest.cc
@@ -0,0 +1,498 @@
+// Copyright 2014 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 <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/connector.h"
+#include "mojo/public/cpp/bindings/filter_chain.h"
+#include "mojo/public/cpp/bindings/interface_ptr.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/message_header_validator.h"
+#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/test_support/test_support.h"
+#include "mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom.h"
+#include "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+template <typename T>
+void Append(std::vector<uint8_t>* data_vector, T data) {
+ size_t pos = data_vector->size();
+ data_vector->resize(pos + sizeof(T));
+ memcpy(&(*data_vector)[pos], &data, sizeof(T));
+}
+
+bool TestInputParser(const std::string& input,
+ bool expected_result,
+ const std::vector<uint8_t>& expected_data,
+ size_t expected_num_handles) {
+ std::vector<uint8_t> data;
+ size_t num_handles;
+ std::string error_message;
+
+ bool result =
+ ParseValidationTestInput(input, &data, &num_handles, &error_message);
+ if (expected_result) {
+ if (result && error_message.empty() && expected_data == data &&
+ expected_num_handles == num_handles) {
+ return true;
+ }
+
+ // Compare with an empty string instead of checking |error_message.empty()|,
+ // so that the message will be printed out if the two are not equal.
+ EXPECT_EQ(std::string(), error_message);
+ EXPECT_EQ(expected_data, data);
+ EXPECT_EQ(expected_num_handles, num_handles);
+ return false;
+ }
+
+ EXPECT_FALSE(error_message.empty());
+ return !result && !error_message.empty();
+}
+
+std::vector<std::string> GetMatchingTests(const std::vector<std::string>& names,
+ const std::string& prefix) {
+ const std::string suffix = ".data";
+ std::vector<std::string> tests;
+ for (size_t i = 0; i < names.size(); ++i) {
+ if (names[i].size() >= suffix.size() &&
+ names[i].substr(0, prefix.size()) == prefix &&
+ names[i].substr(names[i].size() - suffix.size()) == suffix)
+ tests.push_back(names[i].substr(0, names[i].size() - suffix.size()));
+ }
+ return tests;
+}
+
+bool ReadFile(const std::string& path, std::string* result) {
+ FILE* fp = OpenSourceRootRelativeFile(path.c_str());
+ if (!fp) {
+ ADD_FAILURE() << "File not found: " << path;
+ return false;
+ }
+ fseek(fp, 0, SEEK_END);
+ size_t size = static_cast<size_t>(ftell(fp));
+ if (size == 0) {
+ result->clear();
+ fclose(fp);
+ return true;
+ }
+ fseek(fp, 0, SEEK_SET);
+ result->resize(size);
+ size_t size_read = fread(&result->at(0), 1, size, fp);
+ fclose(fp);
+ return size == size_read;
+}
+
+bool ReadAndParseDataFile(const std::string& path,
+ std::vector<uint8_t>* data,
+ size_t* num_handles) {
+ std::string input;
+ if (!ReadFile(path, &input))
+ return false;
+
+ std::string error_message;
+ if (!ParseValidationTestInput(input, data, num_handles, &error_message)) {
+ ADD_FAILURE() << error_message;
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadResultFile(const std::string& path, std::string* result) {
+ if (!ReadFile(path, result))
+ return false;
+
+ // Result files are new-line delimited text files. Remove any CRs.
+ result->erase(std::remove(result->begin(), result->end(), '\r'),
+ result->end());
+
+ // Remove trailing LFs.
+ size_t pos = result->find_last_not_of('\n');
+ if (pos == std::string::npos)
+ result->clear();
+ else
+ result->resize(pos + 1);
+
+ return true;
+}
+
+std::string GetPath(const std::string& root, const std::string& suffix) {
+ return "mojo/public/interfaces/bindings/tests/data/validation/" + root +
+ suffix;
+}
+
+// |message| should be a newly created object.
+bool ReadTestCase(const std::string& test,
+ Message* message,
+ std::string* expected) {
+ std::vector<uint8_t> data;
+ size_t num_handles;
+ if (!ReadAndParseDataFile(GetPath(test, ".data"), &data, &num_handles) ||
+ !ReadResultFile(GetPath(test, ".expected"), expected)) {
+ return false;
+ }
+
+ message->Initialize(static_cast<uint32_t>(data.size()),
+ false /* zero_initialized */);
+ if (!data.empty())
+ memcpy(message->mutable_data(), &data[0], data.size());
+ message->mutable_handles()->resize(num_handles);
+
+ return true;
+}
+
+void RunValidationTests(const std::string& prefix,
+ MessageReceiver* test_message_receiver) {
+ std::vector<std::string> names =
+ EnumerateSourceRootRelativeDirectory(GetPath("", ""));
+ std::vector<std::string> tests = GetMatchingTests(names, prefix);
+ ASSERT_FALSE(tests.empty());
+
+ for (size_t i = 0; i < tests.size(); ++i) {
+ Message message;
+ std::string expected;
+ ASSERT_TRUE(ReadTestCase(tests[i], &message, &expected));
+
+ std::string result;
+ base::RunLoop run_loop;
+ mojo::internal::ValidationErrorObserverForTesting observer(
+ run_loop.QuitClosure());
+ ignore_result(test_message_receiver->Accept(&message));
+ if (expected != "PASS") // Observer only gets called on errors.
+ run_loop.Run();
+ if (observer.last_error() == mojo::internal::VALIDATION_ERROR_NONE)
+ result = "PASS";
+ else
+ result = mojo::internal::ValidationErrorToString(observer.last_error());
+
+ EXPECT_EQ(expected, result) << "failed test: " << tests[i];
+ }
+}
+
+class DummyMessageReceiver : public MessageReceiver {
+ public:
+ bool Accept(Message* message) override {
+ return true; // Any message is OK.
+ }
+};
+
+class ValidationTest : public testing::Test {
+ public:
+ ValidationTest() {}
+
+ protected:
+ base::MessageLoop loop_;
+};
+
+class ValidationIntegrationTest : public ValidationTest {
+ public:
+ ValidationIntegrationTest() : test_message_receiver_(nullptr) {}
+
+ ~ValidationIntegrationTest() override {}
+
+ void SetUp() override {
+ ScopedMessagePipeHandle tester_endpoint;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ CreateMessagePipe(nullptr, &tester_endpoint, &testee_endpoint_));
+ test_message_receiver_ =
+ new TestMessageReceiver(this, std::move(tester_endpoint));
+ }
+
+ void TearDown() override {
+ delete test_message_receiver_;
+ test_message_receiver_ = nullptr;
+
+ // Make sure that the other end receives the OnConnectionError()
+ // notification.
+ PumpMessages();
+ }
+
+ MessageReceiver* test_message_receiver() { return test_message_receiver_; }
+
+ ScopedMessagePipeHandle testee_endpoint() {
+ return std::move(testee_endpoint_);
+ }
+
+ private:
+ class TestMessageReceiver : public MessageReceiver {
+ public:
+ TestMessageReceiver(ValidationIntegrationTest* owner,
+ ScopedMessagePipeHandle handle)
+ : owner_(owner),
+ connector_(std::move(handle),
+ mojo::Connector::SINGLE_THREADED_SEND,
+ base::ThreadTaskRunnerHandle::Get()) {
+ connector_.set_enforce_errors_from_incoming_receiver(false);
+ }
+ ~TestMessageReceiver() override {}
+
+ bool Accept(Message* message) override {
+ return connector_.Accept(message);
+ }
+
+ public:
+ ValidationIntegrationTest* owner_;
+ mojo::Connector connector_;
+ };
+
+ void PumpMessages() { base::RunLoop().RunUntilIdle(); }
+
+ TestMessageReceiver* test_message_receiver_;
+ ScopedMessagePipeHandle testee_endpoint_;
+};
+
+class IntegrationTestInterfaceImpl : public IntegrationTestInterface {
+ public:
+ ~IntegrationTestInterfaceImpl() override {}
+
+ void Method0(BasicStructPtr param0,
+ const Method0Callback& callback) override {
+ callback.Run(std::vector<uint8_t>());
+ }
+};
+
+TEST_F(ValidationTest, InputParser) {
+ {
+ // The parser, as well as Append() defined above, assumes that this code is
+ // running on a little-endian platform. Test whether that is true.
+ uint16_t x = 1;
+ ASSERT_EQ(1, *(reinterpret_cast<char*>(&x)));
+ }
+ {
+ // Test empty input.
+ std::string input;
+ std::vector<uint8_t> expected;
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ // Test input that only consists of comments and whitespaces.
+ std::string input = " \t // hello world \n\r \t// the answer is 42 ";
+ std::vector<uint8_t> expected;
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input =
+ "[u1]0x10// hello world !! \n\r \t [u2]65535 \n"
+ "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint8_t>(0x10));
+ Append(&expected, static_cast<uint16_t>(65535));
+ Append(&expected, static_cast<uint32_t>(65536));
+ Append(&expected, static_cast<uint64_t>(0xffffffffffffffff));
+ Append(&expected, static_cast<uint8_t>(0));
+ Append(&expected, static_cast<uint8_t>(0xff));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40";
+ std::vector<uint8_t> expected;
+ Append(&expected, -static_cast<int64_t>(0x800));
+ Append(&expected, static_cast<int8_t>(-128));
+ Append(&expected, static_cast<int16_t>(0));
+ Append(&expected, static_cast<int32_t>(-40));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[b]00001011 [b]10000000 // hello world\r [b]00000000";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint8_t>(11));
+ Append(&expected, static_cast<uint8_t>(128));
+ Append(&expected, static_cast<uint8_t>(0));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[f]+.3e9 [d]-10.03";
+ std::vector<uint8_t> expected;
+ Append(&expected, +.3e9f);
+ Append(&expected, -10.03);
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint32_t>(14));
+ Append(&expected, static_cast<uint8_t>(0));
+ Append(&expected, static_cast<uint64_t>(9));
+ Append(&expected, static_cast<uint8_t>(0));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 0));
+ }
+ {
+ std::string input = "// This message has handles! \n[handles]50 [u8]2";
+ std::vector<uint8_t> expected;
+ Append(&expected, static_cast<uint64_t>(2));
+
+ EXPECT_TRUE(TestInputParser(input, true, expected, 50));
+ }
+
+ // Test some failure cases.
+ {
+ const char* error_inputs[] = {"/ hello world",
+ "[u1]x",
+ "[u2]-1000",
+ "[u1]0x100",
+ "[s2]-0x8001",
+ "[b]1",
+ "[b]1111111k",
+ "[dist4]unmatched",
+ "[anchr]hello [dist8]hello",
+ "[dist4]a [dist4]a [anchr]a",
+ "[dist4]a [anchr]a [dist4]a [anchr]a",
+ "0 [handles]50",
+ nullptr};
+
+ for (size_t i = 0; error_inputs[i]; ++i) {
+ std::vector<uint8_t> expected;
+ if (!TestInputParser(error_inputs[i], false, expected, 0))
+ ADD_FAILURE() << "Unexpected test result for: " << error_inputs[i];
+ }
+ }
+}
+
+TEST_F(ValidationTest, Conformance) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<ConformanceTestInterface::RequestValidator_>();
+
+ RunValidationTests("conformance_", &validators);
+}
+
+TEST_F(ValidationTest, AssociatedConformace) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<AssociatedConformanceTestInterface::RequestValidator_>();
+
+ RunValidationTests("associated_conformance_", &validators);
+}
+
+// This test is similar to Conformance test but its goal is specifically
+// do bounds-check testing of message validation. For example we test the
+// detection of off-by-one errors in method ordinals.
+TEST_F(ValidationTest, BoundsCheck) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<BoundsCheckTestInterface::RequestValidator_>();
+
+ RunValidationTests("boundscheck_", &validators);
+}
+
+// This test is similar to the Conformance test but for responses.
+TEST_F(ValidationTest, ResponseConformance) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<ConformanceTestInterface::ResponseValidator_>();
+
+ RunValidationTests("resp_conformance_", &validators);
+}
+
+// This test is similar to the BoundsCheck test but for responses.
+TEST_F(ValidationTest, ResponseBoundsCheck) {
+ DummyMessageReceiver dummy_receiver;
+ mojo::FilterChain validators(&dummy_receiver);
+ validators.Append<mojo::MessageHeaderValidator>();
+ validators.Append<BoundsCheckTestInterface::ResponseValidator_>();
+
+ RunValidationTests("resp_boundscheck_", &validators);
+}
+
+// Test that InterfacePtr<X> applies the correct validators and they don't
+// conflict with each other:
+// - MessageHeaderValidator
+// - X::ResponseValidator_
+TEST_F(ValidationIntegrationTest, InterfacePtr) {
+ IntegrationTestInterfacePtr interface_ptr = MakeProxy(
+ InterfacePtrInfo<IntegrationTestInterface>(testee_endpoint(), 0u));
+ interface_ptr.internal_state()->EnableTestingMode();
+
+ RunValidationTests("integration_intf_resp", test_message_receiver());
+ RunValidationTests("integration_msghdr", test_message_receiver());
+}
+
+// Test that Binding<X> applies the correct validators and they don't
+// conflict with each other:
+// - MessageHeaderValidator
+// - X::RequestValidator_
+TEST_F(ValidationIntegrationTest, Binding) {
+ IntegrationTestInterfaceImpl interface_impl;
+ Binding<IntegrationTestInterface> binding(
+ &interface_impl,
+ MakeRequest<IntegrationTestInterface>(testee_endpoint()));
+ binding.EnableTestingMode();
+
+ RunValidationTests("integration_intf_rqst", test_message_receiver());
+ RunValidationTests("integration_msghdr", test_message_receiver());
+}
+
+// Test pointer validation (specifically, that the encoded offset is 32-bit)
+TEST_F(ValidationTest, ValidateEncodedPointer) {
+ uint64_t offset;
+
+ offset = 0ULL;
+ EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset));
+
+ offset = 1ULL;
+ EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset));
+
+ // offset must be <= 32-bit.
+ offset = std::numeric_limits<uint32_t>::max() + 1ULL;
+ EXPECT_FALSE(mojo::internal::ValidateEncodedPointer(&offset));
+}
+
+// Tests the IsKnownEnumValue() function generated for BasicEnum.
+TEST(EnumValueValidationTest, BasicEnum) {
+ // BasicEnum can have -3,0,1,10 as possible integral values.
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-4)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(-3)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-2)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(0)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(1)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(2)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(9)));
+ // In the mojom, we represent this value as hex (0xa).
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(10)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(11)));
+}
+
+// Tests the IsKnownEnumValue() method generated for StructWithEnum.
+TEST(EnumValueValidationTest, EnumWithin) {
+ // StructWithEnum::EnumWithin can have [0,4] as possible integral values.
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(-1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(0)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(1)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(2)));
+ EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(3)));
+ EXPECT_FALSE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(4)));
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo