1 #region Copyright notice and license
2 // Copyright 2015 gRPC authors.
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
8 // http://www.apache.org/licenses/LICENSE-2.0
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
18 using System.Collections.Generic;
19 using System.Diagnostics;
22 using System.Threading;
23 using System.Threading.Tasks;
27 using NUnit.Framework;
29 namespace Grpc.HealthCheck.Tests
32 /// Tests for HealthCheckServiceImpl
34 public class HealthServiceImplTest
37 public void SetStatus()
39 var impl = new HealthServiceImpl();
40 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
41 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, GetStatusHelper(impl, ""));
43 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.NotServing);
44 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, GetStatusHelper(impl, ""));
46 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Unknown);
47 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, GetStatusHelper(impl, ""));
49 impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Serving);
50 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, GetStatusHelper(impl, "grpc.test.TestService"));
54 public void ClearStatus()
56 var impl = new HealthServiceImpl();
57 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
58 impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Unknown);
62 var ex = Assert.Throws<RpcException>(() => GetStatusHelper(impl, ""));
63 Assert.AreEqual(StatusCode.NotFound, ex.Status.StatusCode);
64 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, GetStatusHelper(impl, "grpc.test.TestService"));
68 public void ClearAll()
70 var impl = new HealthServiceImpl();
71 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
72 impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Unknown);
75 Assert.Throws(typeof(RpcException), () => GetStatusHelper(impl, ""));
76 Assert.Throws(typeof(RpcException), () => GetStatusHelper(impl, "grpc.test.TestService"));
80 public void NullsRejected()
82 var impl = new HealthServiceImpl();
83 Assert.Throws(typeof(ArgumentNullException), () => impl.SetStatus(null, HealthCheckResponse.Types.ServingStatus.Serving));
85 Assert.Throws(typeof(ArgumentNullException), () => impl.ClearStatus(null));
88 #if GRPC_SUPPORT_WATCH
90 public async Task Watch()
92 var cts = new CancellationTokenSource();
93 var context = new TestServerCallContext(cts.Token);
94 var writer = new TestResponseStreamWriter();
96 var impl = new HealthServiceImpl();
97 var callTask = impl.Watch(new HealthCheckRequest { Service = "" }, writer, context);
99 // Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
100 var nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
101 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask).Status);
103 nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
104 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
105 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask).Status);
107 nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
108 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.NotServing);
109 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, (await nextWriteTask).Status);
111 nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
112 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Unknown);
113 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Unknown, (await nextWriteTask).Status);
115 // Setting status for a different service name will not update Watch results
116 nextWriteTask = writer.WrittenMessagesReader.ReadAsync();
117 impl.SetStatus("grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.Serving);
118 Assert.IsFalse(nextWriteTask.IsCompleted);
120 impl.ClearStatus("");
121 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask).Status);
123 Assert.IsFalse(callTask.IsCompleted);
129 public async Task Watch_MultipleWatchesForSameService()
131 var cts = new CancellationTokenSource();
132 var context = new TestServerCallContext(cts.Token);
133 var writer1 = new TestResponseStreamWriter();
134 var writer2 = new TestResponseStreamWriter();
136 var impl = new HealthServiceImpl();
137 var callTask1 = impl.Watch(new HealthCheckRequest { Service = "" }, writer1, context);
138 var callTask2 = impl.Watch(new HealthCheckRequest { Service = "" }, writer2, context);
140 // Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
141 var nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
142 var nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
143 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
144 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
146 nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
147 nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
148 impl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving);
149 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask1).Status);
150 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask2).Status);
152 nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
153 nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
154 impl.ClearStatus("");
155 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
156 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
164 public async Task Watch_MultipleWatchesForDifferentServices()
166 var cts = new CancellationTokenSource();
167 var context = new TestServerCallContext(cts.Token);
168 var writer1 = new TestResponseStreamWriter();
169 var writer2 = new TestResponseStreamWriter();
171 var impl = new HealthServiceImpl();
172 var callTask1 = impl.Watch(new HealthCheckRequest { Service = "One" }, writer1, context);
173 var callTask2 = impl.Watch(new HealthCheckRequest { Service = "Two" }, writer2, context);
175 // Calling Watch on a service that doesn't have a value set will initially return ServiceUnknown
176 var nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
177 var nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
178 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
179 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
181 nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
182 nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
183 impl.SetStatus("One", HealthCheckResponse.Types.ServingStatus.Serving);
184 impl.SetStatus("Two", HealthCheckResponse.Types.ServingStatus.NotServing);
185 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.Serving, (await nextWriteTask1).Status);
186 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NotServing, (await nextWriteTask2).Status);
188 nextWriteTask1 = writer1.WrittenMessagesReader.ReadAsync();
189 nextWriteTask2 = writer2.WrittenMessagesReader.ReadAsync();
191 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask1).Status);
192 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, (await nextWriteTask2).Status);
200 public async Task Watch_ExceedMaximumCapacitySize_DiscardOldValues()
202 var cts = new CancellationTokenSource();
203 var context = new TestServerCallContext(cts.Token);
204 var writer = new TestResponseStreamWriter(started: false);
206 var impl = new HealthServiceImpl();
207 var callTask = impl.Watch(new HealthCheckRequest { Service = "" }, writer, context);
209 // Write new statuses. Only last statuses will be returned when we read them from watch writer
210 for (var i = 0; i < HealthServiceImpl.MaxStatusBufferSize * 2; i++)
212 // These statuses aren't "valid" but it is useful for testing to have an incrementing number
213 impl.SetStatus("", (HealthCheckResponse.Types.ServingStatus)i + 10);
216 // Start reading responses now that statuses have been queued up
217 // This is to keep the test non-flakey
220 // Read messages in a background task
221 var statuses = new List<HealthCheckResponse.Types.ServingStatus>();
222 var readStatusesTask = Task.Run(async () => {
223 while (await writer.WrittenMessagesReader.WaitToReadAsync())
225 if (writer.WrittenMessagesReader.TryRead(out var response))
227 statuses.Add(response.Status);
232 // Tell server we're done watching and it can write what it has left and then exit
236 // Ensure we've read all the queued statuses
238 await readStatusesTask;
240 // Collection will contain initial written message (ServiceUnknown) plus 5 queued messages
241 Assert.AreEqual(HealthServiceImpl.MaxStatusBufferSize + 1, statuses.Count);
243 // Initial written message
244 Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.ServiceUnknown, statuses[0]);
246 // Last 5 queued messages
247 Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)15, statuses[statuses.Count - 5]);
248 Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)16, statuses[statuses.Count - 4]);
249 Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)17, statuses[statuses.Count - 3]);
250 Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)18, statuses[statuses.Count - 2]);
251 Assert.AreEqual((HealthCheckResponse.Types.ServingStatus)19, statuses[statuses.Count - 1]);
255 private static HealthCheckResponse.Types.ServingStatus GetStatusHelper(HealthServiceImpl impl, string service)
257 return impl.Check(new HealthCheckRequest { Service = service }, null).Result.Status;