Home | History | Annotate | Download | only in debug
      1 /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
      2 
      3 Licensed under the Apache License, Version 2.0 (the "License");
      4 you may not use this file except in compliance with the License.
      5 You may obtain a copy of the License at
      6 
      7     http://www.apache.org/licenses/LICENSE-2.0
      8 
      9 Unless required by applicable law or agreed to in writing, software
     10 distributed under the License is distributed on an "AS IS" BASIS,
     11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 See the License for the specific language governing permissions and
     13 limitations under the License.
     14 ==============================================================================*/
     15 
     16 #include "tensorflow/core/debug/debug_graph_utils.h"
     17 #include "tensorflow/core/debug/debug_grpc_testlib.h"
     18 #include "tensorflow/core/debug/debug_io_utils.h"
     19 #include "tensorflow/core/lib/core/notification.h"
     20 #include "tensorflow/core/lib/core/status_test_util.h"
     21 #include "tensorflow/core/lib/core/threadpool.h"
     22 #include "tensorflow/core/lib/io/path.h"
     23 #include "tensorflow/core/lib/strings/str_util.h"
     24 #include "tensorflow/core/lib/strings/stringprintf.h"
     25 #include "tensorflow/core/platform/env.h"
     26 #include "tensorflow/core/platform/tracing.h"
     27 
     28 namespace tensorflow {
     29 
     30 class GrpcDebugTest : public ::testing::Test {
     31  protected:
     32   struct ServerData {
     33     int port;
     34     string url;
     35     std::unique_ptr<test::TestEventListenerImpl> server;
     36     std::unique_ptr<thread::ThreadPool> thread_pool;
     37   };
     38 
     39   void SetUp() override {
     40     ClearEnabledWatchKeys();
     41     SetUpInProcessServer(&server_data_, 0);
     42   }
     43 
     44   void TearDown() override { TearDownInProcessServer(&server_data_); }
     45 
     46   void SetUpInProcessServer(ServerData* server_data,
     47                             int64 server_start_delay_micros) {
     48     server_data->port = testing::PickUnusedPortOrDie();
     49     server_data->url = strings::StrCat("grpc://localhost:", server_data->port);
     50     server_data->server.reset(new test::TestEventListenerImpl());
     51 
     52     server_data->thread_pool.reset(
     53         new thread::ThreadPool(Env::Default(), "test_server", 1));
     54     server_data->thread_pool->Schedule(
     55         [server_data, server_start_delay_micros]() {
     56           Env::Default()->SleepForMicroseconds(server_start_delay_micros);
     57           server_data->server->RunServer(server_data->port);
     58         });
     59   }
     60 
     61   void TearDownInProcessServer(ServerData* server_data) {
     62     server_data->server->StopServer();
     63     server_data->thread_pool.reset();
     64   }
     65 
     66   void ClearEnabledWatchKeys() { DebugGrpcIO::ClearEnabledWatchKeys(); }
     67 
     68   const int64 GetChannelConnectionTimeoutMicros() {
     69     return DebugGrpcIO::channel_connection_timeout_micros;
     70   }
     71 
     72   void SetChannelConnectionTimeoutMicros(const int64 timeout) {
     73     DebugGrpcIO::channel_connection_timeout_micros = timeout;
     74   }
     75 
     76   ServerData server_data_;
     77 };
     78 
     79 TEST_F(GrpcDebugTest, ConnectionTimeoutWorks) {
     80   // Use a short timeout so the test won't take too long.
     81   const int64 kOriginalTimeoutMicros = GetChannelConnectionTimeoutMicros();
     82   const int64 kShortTimeoutMicros = 500 * 1000;
     83   SetChannelConnectionTimeoutMicros(kShortTimeoutMicros);
     84   ASSERT_EQ(kShortTimeoutMicros, GetChannelConnectionTimeoutMicros());
     85 
     86   const string& kInvalidGrpcUrl =
     87       strings::StrCat("grpc://localhost:", testing::PickUnusedPortOrDie());
     88   Tensor tensor(DT_FLOAT, TensorShape({1, 1}));
     89   tensor.flat<float>()(0) = 42.0;
     90   Status publish_status = DebugIO::PublishDebugTensor(
     91       DebugNodeKey("/job:localhost/replica:0/task:0/cpu:0", "foo_tensor", 0,
     92                    "DebugIdentity"),
     93       tensor, Env::Default()->NowMicros(), {kInvalidGrpcUrl});
     94   SetChannelConnectionTimeoutMicros(kOriginalTimeoutMicros);
     95   TF_ASSERT_OK(DebugIO::CloseDebugURL(kInvalidGrpcUrl));
     96 
     97   ASSERT_FALSE(publish_status.ok());
     98   const string expected_error_msg = strings::StrCat(
     99       "Failed to connect to gRPC channel at ", kInvalidGrpcUrl.substr(7),
    100       " within a timeout of ", kShortTimeoutMicros / 1e6, " s");
    101   ASSERT_NE(string::npos,
    102             publish_status.error_message().find(expected_error_msg));
    103 }
    104 
    105 TEST_F(GrpcDebugTest, ConnectionToDelayedStartingServerWorks) {
    106   ServerData server_data;
    107   // Server start will be delayed for 1 second.
    108   SetUpInProcessServer(&server_data, 1 * 1000 * 1000);
    109 
    110   Tensor tensor(DT_FLOAT, TensorShape({1, 1}));
    111   tensor.flat<float>()(0) = 42.0;
    112   const DebugNodeKey kDebugNodeKey("/job:localhost/replica:0/task:0/cpu:0",
    113                                    "foo_tensor", 0, "DebugIdentity");
    114   Status publish_status = DebugIO::PublishDebugTensor(
    115       kDebugNodeKey, tensor, Env::Default()->NowMicros(), {server_data.url});
    116   ASSERT_TRUE(publish_status.ok());
    117   TF_ASSERT_OK(DebugIO::CloseDebugURL(server_data.url));
    118 
    119   ASSERT_EQ(1, server_data.server->node_names.size());
    120   ASSERT_EQ(1, server_data.server->output_slots.size());
    121   ASSERT_EQ(1, server_data.server->debug_ops.size());
    122   EXPECT_EQ(kDebugNodeKey.device_name, server_data.server->device_names[0]);
    123   EXPECT_EQ(kDebugNodeKey.node_name, server_data.server->node_names[0]);
    124   EXPECT_EQ(kDebugNodeKey.output_slot, server_data.server->output_slots[0]);
    125   EXPECT_EQ(kDebugNodeKey.debug_op, server_data.server->debug_ops[0]);
    126   TearDownInProcessServer(&server_data);
    127 }
    128 
    129 TEST_F(GrpcDebugTest, SendSingleDebugTensorViaGrpcTest) {
    130   Tensor tensor(DT_FLOAT, TensorShape({1, 1}));
    131   tensor.flat<float>()(0) = 42.0;
    132   const DebugNodeKey kDebugNodeKey("/job:localhost/replica:0/task:0/cpu:0",
    133                                    "foo_tensor", 0, "DebugIdentity");
    134   TF_ASSERT_OK(DebugIO::PublishDebugTensor(
    135       kDebugNodeKey, tensor, Env::Default()->NowMicros(), {server_data_.url}));
    136   TF_ASSERT_OK(DebugIO::CloseDebugURL(server_data_.url));
    137 
    138   // Verify that the expected debug tensor sending happened.
    139   ASSERT_EQ(1, server_data_.server->node_names.size());
    140   ASSERT_EQ(1, server_data_.server->output_slots.size());
    141   ASSERT_EQ(1, server_data_.server->debug_ops.size());
    142   EXPECT_EQ(kDebugNodeKey.device_name, server_data_.server->device_names[0]);
    143   EXPECT_EQ(kDebugNodeKey.node_name, server_data_.server->node_names[0]);
    144   EXPECT_EQ(kDebugNodeKey.output_slot, server_data_.server->output_slots[0]);
    145   EXPECT_EQ(kDebugNodeKey.debug_op, server_data_.server->debug_ops[0]);
    146 }
    147 
    148 TEST_F(GrpcDebugTest, SendDebugTensorWithLargeStringAtIndex0ViaGrpcTest) {
    149   Tensor tensor(DT_STRING, TensorShape({1, 1}));
    150   tensor.flat<string>()(0) = string(5000 * 1024, 'A');
    151   const DebugNodeKey kDebugNodeKey("/job:localhost/replica:0/task:0/cpu:0",
    152                                    "foo_tensor", 0, "DebugIdentity");
    153   const Status status = DebugIO::PublishDebugTensor(
    154       kDebugNodeKey, tensor, Env::Default()->NowMicros(), {server_data_.url});
    155   ASSERT_FALSE(status.ok());
    156   ASSERT_NE(status.error_message().find("string value at index 0 from debug "
    157                                         "node foo_tensor:0:DebugIdentity does "
    158                                         "not fit gRPC message size limit"),
    159             string::npos);
    160   TF_ASSERT_OK(DebugIO::CloseDebugURL(server_data_.url));
    161 }
    162 
    163 TEST_F(GrpcDebugTest, SendDebugTensorWithLargeStringAtIndex1ViaGrpcTest) {
    164   Tensor tensor(DT_STRING, TensorShape({1, 2}));
    165   tensor.flat<string>()(0) = "A";
    166   tensor.flat<string>()(1) = string(5000 * 1024, 'A');
    167   const DebugNodeKey kDebugNodeKey("/job:localhost/replica:0/task:0/cpu:0",
    168                                    "foo_tensor", 0, "DebugIdentity");
    169   const Status status = DebugIO::PublishDebugTensor(
    170       kDebugNodeKey, tensor, Env::Default()->NowMicros(), {server_data_.url});
    171   ASSERT_FALSE(status.ok());
    172   ASSERT_NE(status.error_message().find("string value at index 1 from debug "
    173                                         "node foo_tensor:0:DebugIdentity does "
    174                                         "not fit gRPC message size limit"),
    175             string::npos);
    176   TF_ASSERT_OK(DebugIO::CloseDebugURL(server_data_.url));
    177 }
    178 
    179 TEST_F(GrpcDebugTest, SendMultipleDebugTensorsSynchronizedViaGrpcTest) {
    180   const int32 kSends = 4;
    181 
    182   // Prepare the tensors to sent.
    183   std::vector<Tensor> tensors;
    184   for (int i = 0; i < kSends; ++i) {
    185     Tensor tensor(DT_INT32, TensorShape({1, 1}));
    186     tensor.flat<int>()(0) = i * i;
    187     tensors.push_back(tensor);
    188   }
    189 
    190   thread::ThreadPool* tp =
    191       new thread::ThreadPool(Env::Default(), "grpc_debug_test", kSends);
    192 
    193   mutex mu;
    194   Notification all_done;
    195   int tensor_count GUARDED_BY(mu) = 0;
    196   std::vector<Status> statuses GUARDED_BY(mu);
    197 
    198   const std::vector<string> urls({server_data_.url});
    199 
    200   // Set up the concurrent tasks of sending Tensors via an Event stream to the
    201   // server.
    202   auto fn = [this, &mu, &tensor_count, &tensors, &statuses, &all_done,
    203              &urls]() {
    204     int this_count;
    205     {
    206       mutex_lock l(mu);
    207       this_count = tensor_count++;
    208     }
    209 
    210     // Different concurrent tasks will send different tensors.
    211     const uint64 wall_time = Env::Default()->NowMicros();
    212     Status publish_status = DebugIO::PublishDebugTensor(
    213         DebugNodeKey("/job:localhost/replica:0/task:0/cpu:0",
    214                      strings::StrCat("synchronized_node_", this_count), 0,
    215                      "DebugIdentity"),
    216         tensors[this_count], wall_time, urls);
    217 
    218     {
    219       mutex_lock l(mu);
    220       statuses.push_back(publish_status);
    221       if (this_count == kSends - 1 && !all_done.HasBeenNotified()) {
    222         all_done.Notify();
    223       }
    224     }
    225   };
    226 
    227   // Schedule the concurrent tasks.
    228   for (int i = 0; i < kSends; ++i) {
    229     tp->Schedule(fn);
    230   }
    231 
    232   // Wait for all client tasks to finish.
    233   all_done.WaitForNotification();
    234   delete tp;
    235 
    236   // Close the debug gRPC stream.
    237   Status close_status = DebugIO::CloseDebugURL(server_data_.url);
    238   ASSERT_TRUE(close_status.ok());
    239 
    240   // Check all statuses from the PublishDebugTensor calls().
    241   for (const Status& status : statuses) {
    242     TF_ASSERT_OK(status);
    243   }
    244 
    245   // One prep tensor plus kSends concurrent tensors are expected.
    246   ASSERT_EQ(kSends, server_data_.server->node_names.size());
    247   for (size_t i = 0; i < server_data_.server->node_names.size(); ++i) {
    248     std::vector<string> items =
    249         str_util::Split(server_data_.server->node_names[i], '_');
    250     int tensor_index;
    251     strings::safe_strto32(items[2], &tensor_index);
    252 
    253     ASSERT_EQ(TensorShape({1, 1}),
    254               server_data_.server->debug_tensors[i].shape());
    255     ASSERT_EQ(tensor_index * tensor_index,
    256               server_data_.server->debug_tensors[i].flat<int>()(0));
    257   }
    258 }
    259 
    260 TEST_F(GrpcDebugTest, SendDebugTensorsThroughMultipleRoundsUsingGrpcGating) {
    261   // Prepare the tensor to send.
    262   const DebugNodeKey kDebugNodeKey("/job:localhost/replica:0/task:0/cpu:0",
    263                                    "test_namescope/test_node", 0,
    264                                    "DebugIdentity");
    265   Tensor tensor(DT_INT32, TensorShape({1, 1}));
    266   tensor.flat<int>()(0) = 42;
    267 
    268   const std::vector<string> urls({server_data_.url});
    269   for (int i = 0; i < 3; ++i) {
    270     server_data_.server->ClearReceivedDebugData();
    271     const uint64 wall_time = Env::Default()->NowMicros();
    272 
    273     // On the 1st send (i == 0), gating is disabled, so data should be sent.
    274     // On the 2nd send (i == 1), gating is enabled, and the server has enabled
    275     //   the watch key in the previous send, so data should be sent.
    276     // On the 3rd send (i == 2), gating is enabled, but the server has disabled
    277     //   the watch key in the previous send, so data should not be sent.
    278     const bool enable_gated_grpc = (i != 0);
    279     TF_ASSERT_OK(DebugIO::PublishDebugTensor(kDebugNodeKey, tensor, wall_time,
    280                                              urls, enable_gated_grpc));
    281 
    282     server_data_.server->RequestDebugOpStateChangeAtNextStream(
    283         i == 0 ? EventReply::DebugOpStateChange::READ_ONLY
    284                : EventReply::DebugOpStateChange::DISABLED,
    285         kDebugNodeKey);
    286 
    287     // Close the debug gRPC stream.
    288     Status close_status = DebugIO::CloseDebugURL(server_data_.url);
    289     ASSERT_TRUE(close_status.ok());
    290 
    291     // Check dumped files according to the expected gating results.
    292     if (i < 2) {
    293       ASSERT_EQ(1, server_data_.server->node_names.size());
    294       ASSERT_EQ(1, server_data_.server->output_slots.size());
    295       ASSERT_EQ(1, server_data_.server->debug_ops.size());
    296       EXPECT_EQ(kDebugNodeKey.device_name,
    297                 server_data_.server->device_names[0]);
    298       EXPECT_EQ(kDebugNodeKey.node_name, server_data_.server->node_names[0]);
    299       EXPECT_EQ(kDebugNodeKey.output_slot,
    300                 server_data_.server->output_slots[0]);
    301       EXPECT_EQ(kDebugNodeKey.debug_op, server_data_.server->debug_ops[0]);
    302     } else {
    303       ASSERT_EQ(0, server_data_.server->node_names.size());
    304     }
    305   }
    306 }
    307 
    308 TEST_F(GrpcDebugTest, SendDebugTensorsThroughMultipleRoundsUnderReadWriteMode) {
    309   // Prepare the tensor to send.
    310   const DebugNodeKey kDebugNodeKey("/job:localhost/replica:0/task:0/cpu:0",
    311                                    "test_namescope/test_node", 0,
    312                                    "DebugIdentity");
    313   Tensor tensor(DT_INT32, TensorShape({1, 1}));
    314   tensor.flat<int>()(0) = 42;
    315 
    316   const std::vector<string> urls({server_data_.url});
    317   for (int i = 0; i < 3; ++i) {
    318     server_data_.server->ClearReceivedDebugData();
    319     const uint64 wall_time = Env::Default()->NowMicros();
    320 
    321     // On the 1st send (i == 0), gating is disabled, so data should be sent.
    322     // On the 2nd send (i == 1), gating is enabled, and the server has enabled
    323     //   the watch key in the previous send (READ_WRITE), so data should be
    324     //   sent. In this iteration, the server response with a EventReply proto to
    325     //   unblock the debug node.
    326     // On the 3rd send (i == 2), gating is enabled, but the server has disabled
    327     //   the watch key in the previous send, so data should not be sent.
    328     const bool enable_gated_grpc = (i != 0);
    329     TF_ASSERT_OK(DebugIO::PublishDebugTensor(kDebugNodeKey, tensor, wall_time,
    330                                              urls, enable_gated_grpc));
    331 
    332     server_data_.server->RequestDebugOpStateChangeAtNextStream(
    333         i == 0 ? EventReply::DebugOpStateChange::READ_WRITE
    334                : EventReply::DebugOpStateChange::DISABLED,
    335         kDebugNodeKey);
    336 
    337     // Close the debug gRPC stream.
    338     Status close_status = DebugIO::CloseDebugURL(server_data_.url);
    339     ASSERT_TRUE(close_status.ok());
    340 
    341     // Check dumped files according to the expected gating results.
    342     if (i < 2) {
    343       ASSERT_EQ(1, server_data_.server->node_names.size());
    344       ASSERT_EQ(1, server_data_.server->output_slots.size());
    345       ASSERT_EQ(1, server_data_.server->debug_ops.size());
    346       EXPECT_EQ(kDebugNodeKey.device_name,
    347                 server_data_.server->device_names[0]);
    348       EXPECT_EQ(kDebugNodeKey.node_name, server_data_.server->node_names[0]);
    349       EXPECT_EQ(kDebugNodeKey.output_slot,
    350                 server_data_.server->output_slots[0]);
    351       EXPECT_EQ(kDebugNodeKey.debug_op, server_data_.server->debug_ops[0]);
    352     } else {
    353       ASSERT_EQ(0, server_data_.server->node_names.size());
    354     }
    355   }
    356 }
    357 
    358 TEST_F(GrpcDebugTest, TestGateDebugNodeOnEmptyEnabledSet) {
    359   ASSERT_FALSE(DebugIO::IsDebugNodeGateOpen("foo:0:DebugIdentity",
    360                                             {"grpc://localhost:3333"}));
    361 
    362   // file:// debug URLs are not subject to grpc gating.
    363   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen(
    364       "foo:0:DebugIdentity", {"grpc://localhost:3333", "file:///tmp/tfdbg_1"}));
    365 }
    366 
    367 TEST_F(GrpcDebugTest, TestGateDebugNodeOnNonEmptyEnabledSet) {
    368   const string kGrpcUrl1 = "grpc://localhost:3333";
    369   const string kGrpcUrl2 = "grpc://localhost:3334";
    370 
    371   DebugGrpcIO::SetDebugNodeKeyGrpcState(
    372       kGrpcUrl1, "foo:0:DebugIdentity",
    373       EventReply::DebugOpStateChange::READ_ONLY);
    374   DebugGrpcIO::SetDebugNodeKeyGrpcState(
    375       kGrpcUrl1, "bar:0:DebugIdentity",
    376       EventReply::DebugOpStateChange::READ_ONLY);
    377 
    378   ASSERT_FALSE(
    379       DebugIO::IsDebugNodeGateOpen("foo:1:DebugIdentity", {kGrpcUrl1}));
    380   ASSERT_FALSE(
    381       DebugIO::IsDebugNodeGateOpen("foo:1:DebugNumericSummary", {kGrpcUrl1}));
    382   ASSERT_FALSE(
    383       DebugIO::IsDebugNodeGateOpen("qux:0:DebugIdentity", {kGrpcUrl1}));
    384   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen("foo:0:DebugIdentity", {kGrpcUrl1}));
    385   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen("bar:0:DebugIdentity", {kGrpcUrl1}));
    386 
    387   // Wrong grpc:// debug URLs.
    388   ASSERT_FALSE(
    389       DebugIO::IsDebugNodeGateOpen("foo:0:DebugIdentity", {kGrpcUrl2}));
    390   ASSERT_FALSE(
    391       DebugIO::IsDebugNodeGateOpen("bar:0:DebugIdentity", {kGrpcUrl2}));
    392 
    393   // file:// debug URLs are not subject to grpc gating.
    394   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen("qux:0:DebugIdentity",
    395                                            {"file:///tmp/tfdbg_1", kGrpcUrl1}));
    396 }
    397 
    398 TEST_F(GrpcDebugTest, TestGateDebugNodeOnMultipleEmptyEnabledSets) {
    399   const string kGrpcUrl1 = "grpc://localhost:3333";
    400   const string kGrpcUrl2 = "grpc://localhost:3334";
    401   const string kGrpcUrl3 = "grpc://localhost:3335";
    402 
    403   DebugGrpcIO::SetDebugNodeKeyGrpcState(
    404       kGrpcUrl1, "foo:0:DebugIdentity",
    405       EventReply::DebugOpStateChange::READ_ONLY);
    406   DebugGrpcIO::SetDebugNodeKeyGrpcState(
    407       kGrpcUrl2, "bar:0:DebugIdentity",
    408       EventReply::DebugOpStateChange::READ_ONLY);
    409 
    410   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen("foo:0:DebugIdentity", {kGrpcUrl1}));
    411   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen("bar:0:DebugIdentity", {kGrpcUrl2}));
    412   ASSERT_FALSE(
    413       DebugIO::IsDebugNodeGateOpen("foo:0:DebugIdentity", {kGrpcUrl2}));
    414   ASSERT_FALSE(
    415       DebugIO::IsDebugNodeGateOpen("bar:0:DebugIdentity", {kGrpcUrl1}));
    416   ASSERT_FALSE(
    417       DebugIO::IsDebugNodeGateOpen("foo:0:DebugIdentity", {kGrpcUrl3}));
    418   ASSERT_FALSE(
    419       DebugIO::IsDebugNodeGateOpen("bar:0:DebugIdentity", {kGrpcUrl3}));
    420   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen("foo:0:DebugIdentity",
    421                                            {kGrpcUrl1, kGrpcUrl2}));
    422   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen("bar:0:DebugIdentity",
    423                                            {kGrpcUrl1, kGrpcUrl2}));
    424   ASSERT_TRUE(DebugIO::IsDebugNodeGateOpen("foo:0:DebugIdentity",
    425                                            {kGrpcUrl1, kGrpcUrl3}));
    426   ASSERT_FALSE(DebugIO::IsDebugNodeGateOpen("bar:0:DebugIdentity",
    427                                             {kGrpcUrl1, kGrpcUrl3}));
    428 }
    429 
    430 TEST_F(GrpcDebugTest, TestGateDebugNodeOnNonEmptyEnabledSetAndEmptyURLs) {
    431   DebugGrpcIO::SetDebugNodeKeyGrpcState(
    432       "grpc://localhost:3333", "foo:0:DebugIdentity",
    433       EventReply::DebugOpStateChange::READ_ONLY);
    434 
    435   std::vector<string> debug_urls_1;
    436   ASSERT_FALSE(
    437       DebugIO::IsDebugNodeGateOpen("foo:1:DebugIdentity", debug_urls_1));
    438 }
    439 
    440 TEST_F(GrpcDebugTest, TestGateCopyNodeOnEmptyEnabledSet) {
    441   const string kGrpcUrl1 = "grpc://localhost:3333";
    442   const string kWatch1 = "foo:0:DebugIdentity";
    443 
    444   ASSERT_FALSE(DebugIO::IsCopyNodeGateOpen(
    445       {DebugWatchAndURLSpec(kWatch1, kGrpcUrl1, true)}));
    446   ASSERT_TRUE(DebugIO::IsCopyNodeGateOpen(
    447       {DebugWatchAndURLSpec(kWatch1, kGrpcUrl1, false)}));
    448 
    449   // file:// debug URLs are not subject to grpc gating.
    450   ASSERT_TRUE(DebugIO::IsCopyNodeGateOpen(
    451       {DebugWatchAndURLSpec("foo:0:DebugIdentity", kGrpcUrl1, true),
    452        DebugWatchAndURLSpec("foo:0:DebugIdentity", "file:///tmp/tfdbg_1",
    453                             false)}));
    454 }
    455 
    456 TEST_F(GrpcDebugTest, TestGateCopyNodeOnNonEmptyEnabledSet) {
    457   const string kGrpcUrl1 = "grpc://localhost:3333";
    458   const string kGrpcUrl2 = "grpc://localhost:3334";
    459   const string kWatch1 = "foo:0:DebugIdentity";
    460   const string kWatch2 = "foo:1:DebugIdentity";
    461   DebugGrpcIO::SetDebugNodeKeyGrpcState(
    462       kGrpcUrl1, kWatch1, EventReply::DebugOpStateChange::READ_ONLY);
    463 
    464   ASSERT_TRUE(DebugIO::IsCopyNodeGateOpen(
    465       {DebugWatchAndURLSpec(kWatch1, kGrpcUrl1, true)}));
    466 
    467   ASSERT_FALSE(DebugIO::IsCopyNodeGateOpen(
    468       {DebugWatchAndURLSpec(kWatch1, kGrpcUrl2, true)}));
    469   ASSERT_TRUE(DebugIO::IsCopyNodeGateOpen(
    470       {DebugWatchAndURLSpec(kWatch1, kGrpcUrl2, false)}));
    471 
    472   ASSERT_FALSE(DebugIO::IsCopyNodeGateOpen(
    473       {DebugWatchAndURLSpec(kWatch2, kGrpcUrl1, true)}));
    474   ASSERT_TRUE(DebugIO::IsCopyNodeGateOpen(
    475       {DebugWatchAndURLSpec(kWatch2, kGrpcUrl1, false)}));
    476 
    477   ASSERT_TRUE(DebugIO::IsCopyNodeGateOpen(
    478       {DebugWatchAndURLSpec(kWatch1, kGrpcUrl1, true),
    479        DebugWatchAndURLSpec(kWatch1, kGrpcUrl2, true)}));
    480   ASSERT_TRUE(DebugIO::IsCopyNodeGateOpen(
    481       {DebugWatchAndURLSpec(kWatch1, kGrpcUrl1, true),
    482        DebugWatchAndURLSpec(kWatch2, kGrpcUrl2, true)}));
    483 }
    484 
    485 }  // namespace tensorflow
    486