From 9c3abcd06fa52ebae513052983f1d86f779f4f28 Mon Sep 17 00:00:00 2001 From: MediaPipe Team Date: Fri, 3 Mar 2023 16:59:29 -0800 Subject: [PATCH] Document graph service usage with docs and unit tests. PiperOrigin-RevId: 513955877 --- mediapipe/framework/BUILD | 1 + mediapipe/framework/calculator_contract.h | 17 ++ mediapipe/framework/graph_service_test.cc | 186 ++++++++++++++++++++++ 3 files changed, 204 insertions(+) diff --git a/mediapipe/framework/BUILD b/mediapipe/framework/BUILD index 518eb6b0e..5830a4c05 100644 --- a/mediapipe/framework/BUILD +++ b/mediapipe/framework/BUILD @@ -1533,6 +1533,7 @@ cc_test( "//mediapipe/framework/port:parse_text_proto", "//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:status", + "//mediapipe/framework/port:status_matchers", "//mediapipe/framework/tool:sink", ], ) diff --git a/mediapipe/framework/calculator_contract.h b/mediapipe/framework/calculator_contract.h index 10fb2d049..7726065a7 100644 --- a/mediapipe/framework/calculator_contract.h +++ b/mediapipe/framework/calculator_contract.h @@ -141,6 +141,12 @@ class CalculatorContract { class GraphServiceRequest { public: // APIs that should be used by calculators. + // + // Indicates that requested service is optional and calculator can operate + // correctly without it. + // + // NOTE: `CalculatorGraph` will still try to create services which allow + // default initialization. (See `CalculatorGraph::UseService`) GraphServiceRequest& Optional() { optional_ = true; return *this; @@ -158,6 +164,17 @@ class CalculatorContract { bool optional_ = false; }; + // Indicates specific `service` is required for graph execution. + // + // For services which allow default initialization: + // - `CalculatorGraph` will try to create corresponding service object by + // default even if request is made optional + // (`GraphServiceRequest::Optional()`) + // + // For services which disallow default initialization: + // - `CalculatorGraph` requires client to set corresponding service object and + // otherwise fails, unles request is mad optional + // (`GraphServiceRequest::Optional()`) GraphServiceRequest& UseService(const GraphServiceBase& service) { auto it = service_requests_.emplace(service.key, service).first; return it->second; diff --git a/mediapipe/framework/graph_service_test.cc b/mediapipe/framework/graph_service_test.cc index bd9b1af66..ae0809ea5 100644 --- a/mediapipe/framework/graph_service_test.cc +++ b/mediapipe/framework/graph_service_test.cc @@ -157,5 +157,191 @@ TEST_F(GraphServiceTest, CreateDefault) { MP_EXPECT_OK(kNeedsCreateService.CreateDefaultObject()); } +struct TestServiceData {}; + +const GraphService kTestServiceAllowDefaultInitialization( + "kTestServiceAllowDefaultInitialization", + GraphServiceBase::kAllowDefaultInitialization); + +// This is only for test purposes. Ideally, a calculator that fails when service +// is not available, should request the service as non-Optional. +class FailOnUnavailableOptionalServiceCalculator : public CalculatorBase { + public: + static absl::Status GetContract(CalculatorContract* cc) { + cc->UseService(kTestServiceAllowDefaultInitialization).Optional(); + return absl::OkStatus(); + } + absl::Status Open(CalculatorContext* cc) final { + RET_CHECK(cc->Service(kTestServiceAllowDefaultInitialization).IsAvailable()) + << "Service is unavailable."; + return absl::OkStatus(); + } + absl::Status Process(CalculatorContext* cc) final { return absl::OkStatus(); } +}; +REGISTER_CALCULATOR(FailOnUnavailableOptionalServiceCalculator); + +// Documents and ensures current behavior for requesting optional +// "AllowDefaultInitialization" services: +// - Service object is created by default. +TEST(AllowDefaultInitializationGraphServiceTest, + ServiceIsAvailableWithOptionalUse) { + CalculatorGraphConfig config = + mediapipe::ParseTextProtoOrDie(R"pb( + node { calculator: 'FailOnUnavailableOptionalServiceCalculator' } + )pb"); + + CalculatorGraph graph; + MP_ASSERT_OK(graph.Initialize(config)); + MP_ASSERT_OK(graph.StartRun({})); + MP_EXPECT_OK(graph.WaitUntilIdle()); +} + +// Documents and ensures current behavior for setting `nullptr` service objects +// for "AllowDefaultInitialization" optional services. +// - It's allowed. +// - It disables creation of "AllowDefaultInitialization" service objects, hence +// results in optional service unavailability. +TEST(AllowDefaultInitializationGraphServiceTest, + NullServiceObjectIsAllowAndResultsInOptionalServiceUnavailability) { + CalculatorGraphConfig config = + mediapipe::ParseTextProtoOrDie(R"pb( + node { calculator: 'FailOnUnavailableOptionalServiceCalculator' } + )pb"); + + CalculatorGraph graph; + std::shared_ptr object = nullptr; + MP_ASSERT_OK( + graph.SetServiceObject(kTestServiceAllowDefaultInitialization, object)); + MP_ASSERT_OK(graph.Initialize(config)); + MP_ASSERT_OK(graph.StartRun({})); + EXPECT_THAT(graph.WaitUntilIdle(), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Service is unavailable."))); +} + +class FailOnUnavailableServiceCalculator : public CalculatorBase { + public: + static absl::Status GetContract(CalculatorContract* cc) { + cc->UseService(kTestServiceAllowDefaultInitialization); + return absl::OkStatus(); + } + absl::Status Open(CalculatorContext* cc) final { + RET_CHECK(cc->Service(kTestServiceAllowDefaultInitialization).IsAvailable()) + << "Service is unavailable."; + return absl::OkStatus(); + } + absl::Status Process(CalculatorContext* cc) final { return absl::OkStatus(); } +}; +REGISTER_CALCULATOR(FailOnUnavailableServiceCalculator); + +// Documents and ensures current behavior for requesting optional +// "AllowDefaultInitialization" services: +// - Service object is created by default. +TEST(AllowDefaultInitializationGraphServiceTest, ServiceIsAvailable) { + CalculatorGraphConfig config = + mediapipe::ParseTextProtoOrDie(R"pb( + node { calculator: 'FailOnUnavailableServiceCalculator' } + )pb"); + + CalculatorGraph graph; + MP_ASSERT_OK(graph.Initialize(config)); + MP_ASSERT_OK(graph.StartRun({})); + MP_EXPECT_OK(graph.WaitUntilIdle()); +} + +// Documents and ensures current behavior for setting `nullptr` service objects +// for "AllowDefaultInitialization" services. +// - It's allowed. +// - It disables creation of "AllowDefaultInitialization" service objects, hence +// in service unavaialbility. +TEST(AllowDefaultInitializationGraphServiceTest, + NullServiceObjectIsAllowAndResultsInServiceUnavailability) { + CalculatorGraphConfig config = + mediapipe::ParseTextProtoOrDie(R"pb( + node { calculator: 'FailOnUnavailableServiceCalculator' } + )pb"); + + CalculatorGraph graph; + std::shared_ptr object = nullptr; + MP_ASSERT_OK( + graph.SetServiceObject(kTestServiceAllowDefaultInitialization, object)); + MP_ASSERT_OK(graph.Initialize(config)); + MP_ASSERT_OK(graph.StartRun({})); + EXPECT_THAT(graph.WaitUntilIdle(), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Service is unavailable."))); +} + +const GraphService kTestServiceDisallowDefaultInitialization( + "kTestServiceDisallowDefaultInitialization", + GraphServiceBase::kDisallowDefaultInitialization); + +class FailOnUnavailableOptionalDisallowDefaultInitServiceCalculator + : public CalculatorBase { + public: + static absl::Status GetContract(CalculatorContract* cc) { + cc->UseService(kTestServiceDisallowDefaultInitialization).Optional(); + return absl::OkStatus(); + } + absl::Status Open(CalculatorContext* cc) final { + RET_CHECK( + cc->Service(kTestServiceDisallowDefaultInitialization).IsAvailable()) + << "Service is unavailable."; + return absl::OkStatus(); + } + absl::Status Process(CalculatorContext* cc) final { return absl::OkStatus(); } +}; +REGISTER_CALCULATOR( + FailOnUnavailableOptionalDisallowDefaultInitServiceCalculator); + +// Documents and ensures current behavior for requesting optional +// "DisallowDefaultInitialization" services: +// - Service object is not created by default. +TEST(DisallowDefaultInitializationGraphServiceTest, + ServiceIsUnavailableWithOptionalUse) { + CalculatorGraphConfig config = mediapipe::ParseTextProtoOrDie< + CalculatorGraphConfig>(R"pb( + node { + calculator: 'FailOnUnavailableOptionalDisallowDefaultInitServiceCalculator' + } + )pb"); + + CalculatorGraph graph; + MP_ASSERT_OK(graph.Initialize(config)); + MP_ASSERT_OK(graph.StartRun({})); + EXPECT_THAT(graph.WaitUntilIdle(), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("Service is unavailable."))); +} + +class UseDisallowDefaultInitServiceCalculator : public CalculatorBase { + public: + static absl::Status GetContract(CalculatorContract* cc) { + cc->UseService(kTestServiceDisallowDefaultInitialization); + return absl::OkStatus(); + } + absl::Status Open(CalculatorContext* cc) final { return absl::OkStatus(); } + absl::Status Process(CalculatorContext* cc) final { return absl::OkStatus(); } +}; +REGISTER_CALCULATOR(UseDisallowDefaultInitServiceCalculator); + +// Documents and ensures current behavior for requesting +// "DisallowDefaultInitialization" services: +// - Service object is not created by default. +// - Graph run fails. +TEST(DisallowDefaultInitializationGraphServiceTest, + StartRunFailsMissingService) { + CalculatorGraphConfig config = + mediapipe::ParseTextProtoOrDie(R"pb( + node { calculator: 'UseDisallowDefaultInitServiceCalculator' } + )pb"); + + CalculatorGraph graph; + MP_ASSERT_OK(graph.Initialize(config)); + EXPECT_THAT(graph.StartRun({}), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("was not provided and cannot be created"))); +} + } // namespace } // namespace mediapipe