Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
TelemetryTests.cs
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | using System; |
| 2 | using System.IO; |
| 3 | using System.Net; |
| 4 | using System.Net.Sockets; |
| 5 | using System.Text; |
| 6 | using System.Threading.Tasks; |
| 7 | using Xunit; |
| 8 | |
| 9 | namespace OpenCKey.Tests |
| 10 | { |
| 11 | // Telemetry state is process-global: every test touching it lives in this |
| 12 | // collection, which xUnit never runs in parallel with itself. |
| 13 | [CollectionDefinition("telemetry", DisableParallelization = true)] |
| 14 | public class TelemetryCollection |
| 15 | { |
| 16 | } |
| 17 | |
| 18 | [Collection("telemetry")] |
| 19 | public class TelemetryTests : IDisposable |
| 20 | { |
| 21 | public TelemetryTests() |
| 22 | { |
| 23 | TelemetrySender.AutoSend = false; // the tests call FlushAsync themselves |
| 24 | CKeyTelemetry.ResetForTesting(); |
| 25 | } |
| 26 | |
| 27 | public void Dispose() |
| 28 | { |
| 29 | TelemetrySender.Endpoint = new Uri(CKeyTelemetry.DefaultEndpoint); |
| 30 | CKeyTelemetry.ResetForTesting(); |
| 31 | CKey.Telemetry = CKey.TelemetryLocal; // nothing from the test run is ever sent |
| 32 | } |
| 33 | |
| 34 | [Fact] |
| 35 | public void DefaultsToOne() |
| 36 | { |
| 37 | Assert.True(CKeyTelemetry.IsAvailable); |
| 38 | Assert.Equal(1, CKey.Telemetry); |
| 39 | } |
| 40 | |
| 41 | [Theory] |
| 42 | [InlineData(0)] |
| 43 | [InlineData(1)] |
| 44 | [InlineData(2)] |
| 45 | public void AcceptsZeroOneTwo(int mode) |
| 46 | { |
| 47 | CKey.Telemetry = mode; |
| 48 | Assert.Equal(mode, CKey.Telemetry); |
| 49 | } |
| 50 | |
| 51 | [Theory] |
| 52 | [InlineData(-1)] |
| 53 | [InlineData(3)] |
| 54 | [InlineData(int.MaxValue)] |
| 55 | public void RejectsOtherValuesAndKeepsTheMode(int mode) |
| 56 | { |
| 57 | CKey.Telemetry = 2; |
| 58 | Assert.Throws<ArgumentOutOfRangeException>(() => CKey.Telemetry = mode); |
| 59 | Assert.Equal(2, CKey.Telemetry); // never silently reset to 1 |
| 60 | } |
| 61 | |
| 62 | [Fact] |
| 63 | public void ScanSessionCountsASuccess() |
| 64 | { |
| 65 | CKey.Telemetry = 2; |
| 66 | var symbol = CKeySymbol.Encode("session"); |
| 67 | byte[] frame = Frames.Frame(symbol, out int w, out int h, out int stride); |
| 68 | using (var session = new CKeyScanSession()) |
| 69 | { |
| 70 | Assert.Equal("session", session.ProcessFrame(frame, w, h, stride)?.Text); |
| 71 | Assert.True(session.HasDecoded); |
| 72 | Assert.NotNull(session.ProcessFrame(frame, w, h, stride)); // later decodes still returned |
| 73 | } |
| 74 | CKeyTelemetryCounters c = CKeyTelemetry.Snapshot(); |
| 75 | Assert.Equal(1UL, c.SessionsStarted); |
| 76 | Assert.Equal(1UL, c.SessionsSucceeded); |
| 77 | Assert.Equal(0UL, c.SessionsFailed); |
| 78 | Assert.Equal(2UL, c.Frames); |
| 79 | Assert.Equal(1UL, c.Decoded[0]); // p0 |
| 80 | Assert.Equal(1UL, c.TimeToDecodeMs.Count); |
| 81 | Assert.Equal(2UL, c.FrameMs.Count); |
| 82 | } |
| 83 | |
| 84 | [Fact] |
| 85 | public void PrintLayoutCountsAsP0_1() |
| 86 | { |
| 87 | CKey.Telemetry = 2; |
| 88 | var symbol = CKeySymbol.Encode("print", layout: Layout.Print); |
| 89 | byte[] frame = Frames.Frame(symbol, out int w, out int h, out int stride); |
| 90 | using (var session = new CKeyScanSession()) Assert.NotNull(session.ProcessFrame(frame, w, h, stride)); |
| 91 | Assert.Equal(1UL, CKeyTelemetry.Snapshot().Decoded[1]); |
| 92 | } |
| 93 | |
| 94 | [Fact] |
| 95 | public void SessionWithoutDecodeFails() |
| 96 | { |
| 97 | CKey.Telemetry = 2; |
| 98 | var blank = new byte[64 * 64]; |
| 99 | using (var session = new CKeyScanSession()) |
| 100 | { |
| 101 | Assert.Null(session.ProcessFrame(blank, 64, 64, 64)); |
| 102 | session.Restart(); // ends the first session as failed |
| 103 | } |
| 104 | CKeyTelemetryCounters c = CKeyTelemetry.Snapshot(); |
| 105 | Assert.Equal(2UL, c.SessionsStarted); |
| 106 | Assert.Equal(2UL, c.SessionsFailed); |
| 107 | Assert.Equal(0UL, c.SessionsSucceeded); |
| 108 | } |
| 109 | |
| 110 | [Fact] |
| 111 | public void ModeZeroCountsNothingAndPurges() |
| 112 | { |
| 113 | CKey.Telemetry = 2; |
| 114 | using (new CKeyScanSession()) |
| 115 | { |
| 116 | } |
| 117 | Assert.Equal(1UL, CKeyTelemetry.Snapshot().SessionsStarted); |
| 118 | CKey.Telemetry = 0; |
| 119 | using (new CKeyScanSession()) |
| 120 | { |
| 121 | } |
| 122 | Assert.Equal(0UL, CKeyTelemetry.Snapshot().SessionsStarted); |
| 123 | } |
| 124 | |
| 125 | [Fact] |
| 126 | public void ExportImportRoundTrip() |
| 127 | { |
| 128 | CKey.Telemetry = 2; |
| 129 | using (new CKeyScanSession()) |
| 130 | { |
| 131 | } |
| 132 | string state = CKeyTelemetry.ExportState(); |
| 133 | Assert.StartsWith("{\"state\":1", state); |
| 134 | CKeyTelemetry.ResetForTesting(); |
| 135 | CKey.Telemetry = 2; |
| 136 | CKeyTelemetry.ImportState(state); |
| 137 | Assert.Equal(1UL, CKeyTelemetry.Snapshot().SessionsStarted); |
| 138 | Assert.Throws<CKeyException>(() => CKeyTelemetry.ImportState("{\"nope\":1}")); |
| 139 | } |
| 140 | |
| 141 | [Fact] |
| 142 | public async Task FlushSendsOneBatchToTheEndpoint() |
| 143 | { |
| 144 | // Build an unsent delta that is due immediately: deltas restored by |
| 145 | // import are exempt from the minimum age, and a fresh process has no |
| 146 | // hourly wait. |
| 147 | CKey.Telemetry = 1; |
| 148 | using (new CKeyScanSession()) |
| 149 | { |
| 150 | } |
| 151 | string state = CKeyTelemetry.ExportState(); |
| 152 | CKeyTelemetry.ResetForTesting(); |
| 153 | CKeyTelemetry.ImportState(state); |
| 154 | |
| 155 | int port = FreePort(); |
| 156 | using var listener = new HttpListener(); |
| 157 | listener.Prefixes.Add($"http://localhost:{port}/"); |
| 158 | listener.Start(); |
| 159 | TelemetrySender.Endpoint = new Uri($"http://localhost:{port}/api/telemetry/v1/batch"); |
| 160 | |
| 161 | Task<string> server = Task.Run(async () => |
| 162 | { |
| 163 | HttpListenerContext context = await listener.GetContextAsync(); |
| 164 | string body; |
| 165 | using (var reader = new StreamReader(context.Request.InputStream, Encoding.UTF8)) body = await reader.ReadToEndAsync(); |
| 166 | string contentType = context.Request.ContentType ?? string.Empty; |
| 167 | context.Response.StatusCode = 204; |
| 168 | context.Response.Close(); |
| 169 | return contentType + "\n" + body; |
| 170 | }); |
| 171 | |
| 172 | bool accepted = await CKeyTelemetry.FlushAsync().WaitAsync(TimeSpan.FromSeconds(30)); |
| 173 | Assert.True(accepted); |
| 174 | string[] request = (await server.WaitAsync(TimeSpan.FromSeconds(30))).Split('\n'); |
| 175 | Assert.StartsWith("application/json", request[0]); |
| 176 | Assert.Contains("\"schema\":1", request[1]); |
| 177 | Assert.Contains("\"sdk\":\"dotnet\"", request[1]); |
| 178 | Assert.Contains($"\"sdk_version\":\"{CKey.Version}\"", request[1]); |
| 179 | Assert.Contains("\"sessions\":{\"started\":1,\"succeeded\":0,\"failed\":1}", request[1]); |
| 180 | |
| 181 | // Accepted: nothing more is due (and the hourly cadence has started). |
| 182 | Assert.False(await CKeyTelemetry.FlushAsync()); |
| 183 | } |
| 184 | |
| 185 | [Fact] |
| 186 | public async Task NetworkFailureKeepsTheBatch() |
| 187 | { |
| 188 | CKey.Telemetry = 1; |
| 189 | using (new CKeyScanSession()) |
| 190 | { |
| 191 | } |
| 192 | string state = CKeyTelemetry.ExportState(); |
| 193 | CKeyTelemetry.ResetForTesting(); |
| 194 | CKeyTelemetry.ImportState(state); |
| 195 | TelemetrySender.Endpoint = new Uri($"http://127.0.0.1:{FreePort()}/"); // nothing listens |
| 196 | Assert.False(await CKeyTelemetry.FlushAsync()); |
| 197 | // Kept: the delta still holds the session (it was merged back). |
| 198 | Assert.Contains("\"unsent\":{\"sessions\":{\"started\":1", CKeyTelemetry.ExportState()); |
| 199 | } |
| 200 | |
| 201 | [Fact] |
| 202 | public void VersionMatchesTheTelemetrySchema() |
| 203 | { |
| 204 | Assert.Matches(@"^0\.[0-9]+\.[0-9]+(-(build|dev)\.[0-9]{1,6})?$", CKey.Version); |
| 205 | Assert.False(string.IsNullOrEmpty(CKey.CoreVersion)); |
| 206 | } |
| 207 | |
| 208 | private static int FreePort() |
| 209 | { |
| 210 | var l = new TcpListener(IPAddress.Loopback, 0); |
| 211 | l.Start(); |
| 212 | int port = ((IPEndPoint)l.LocalEndpoint).Port; |
| 213 | l.Stop(); |
| 214 | return port; |
| 215 | } |
| 216 | } |
| 217 | } |