/src/mozilla-central/toolkit/components/telemetry/geckoview/gtest/TestGeckoView.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* vim:set ts=2 sw=2 sts=2 et: */ |
2 | | /* Any copyright is dedicated to the Public Domain. |
3 | | * http://creativecommons.org/publicdomain/zero/1.0/ |
4 | | */ |
5 | | |
6 | | #include "gtest/gtest.h" |
7 | | #include "mozilla/JSONWriter.h" |
8 | | #include "nsDirectoryServiceDefs.h" |
9 | | #include "nsIObserver.h" |
10 | | #include "nsIObserverService.h" |
11 | | #include "nsIOutputStream.h" |
12 | | #include "nsITelemetry.h" |
13 | | #include "nsJSUtils.h" |
14 | | #include "nsNetUtil.h" |
15 | | #include "nsThreadUtils.h" |
16 | | #include "nsPrintfCString.h" |
17 | | #include "prenv.h" |
18 | | #include "mozilla/Telemetry.h" |
19 | | #include "TelemetryFixture.h" |
20 | | #include "TelemetryGeckoViewPersistence.h" |
21 | | #include "core/TelemetryScalar.h" |
22 | | #include "TelemetryTestHelpers.h" |
23 | | |
24 | | using namespace mozilla; |
25 | | using namespace TelemetryTestHelpers; |
26 | | |
27 | | const char kSampleData[] = R"({ |
28 | | "scalars": { |
29 | | "content": { |
30 | | "telemetry.test.all_processes_uint": 37 |
31 | | } |
32 | | }, |
33 | | "keyedScalars": { |
34 | | "parent": { |
35 | | "telemetry.test.keyed_unsigned_int": { |
36 | | "testKey": 73 |
37 | | } |
38 | | } |
39 | | } |
40 | | })"; |
41 | | |
42 | | const char16_t kPersistedFilename[] = u"gv_measurements.json"; |
43 | | const char kDataLoadedTopic[] = "internal-telemetry-geckoview-load-complete"; |
44 | | |
45 | | namespace { |
46 | | |
47 | | /** |
48 | | * Using gtest assertion macros requires the containing function to return |
49 | | * a void type. For this reason, all the functions below are using that return |
50 | | * type. |
51 | | */ |
52 | | void |
53 | | GetMockedDataDir(nsAString& aMockedDir) |
54 | 0 | { |
55 | 0 | // Get the OS temporary directory. |
56 | 0 | nsCOMPtr<nsIFile> tmpDir; |
57 | 0 | nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, |
58 | 0 | getter_AddRefs(tmpDir)); |
59 | 0 | ASSERT_EQ(NS_SUCCEEDED(rv), true); |
60 | 0 | // Return the mocked dir. |
61 | 0 | rv = tmpDir->GetPath(aMockedDir); |
62 | 0 | ASSERT_EQ(NS_SUCCEEDED(rv), true); |
63 | 0 | } |
64 | | |
65 | | void |
66 | | MockAndroidDataDir() |
67 | 0 | { |
68 | 0 | // Get the OS temporary directory. |
69 | 0 | nsAutoString mockedPath; |
70 | 0 | GetMockedDataDir(mockedPath); |
71 | 0 |
|
72 | 0 | // Set the environment variable to mock. |
73 | 0 | // Note: we intentionally leak it with |ToNewCString| as PR_SetEnv forces |
74 | 0 | // us to! |
75 | 0 | nsAutoCString mockedEnv( |
76 | 0 | nsPrintfCString("MOZ_ANDROID_DATA_DIR=%s", NS_ConvertUTF16toUTF8(mockedPath).get())); |
77 | 0 | ASSERT_EQ(PR_SetEnv(ToNewCString(mockedEnv)), PR_SUCCESS); |
78 | 0 | } |
79 | | |
80 | | void |
81 | | WritePersistenceFile(const nsACString& aData) |
82 | 0 | { |
83 | 0 | // Write the file to the temporary directory. |
84 | 0 | nsCOMPtr<nsIFile> file; |
85 | 0 | nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, |
86 | 0 | getter_AddRefs(file)); |
87 | 0 | ASSERT_EQ(NS_SUCCEEDED(rv), true); |
88 | 0 |
|
89 | 0 | // Append the filename and the extension. |
90 | 0 | nsAutoString fileName; |
91 | 0 | fileName.Append(kPersistedFilename); |
92 | 0 | file->Append(fileName); |
93 | 0 |
|
94 | 0 | nsCOMPtr<nsIOutputStream> stream; |
95 | 0 | rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), file); |
96 | 0 | ASSERT_EQ(NS_SUCCEEDED(rv), true); |
97 | 0 |
|
98 | 0 | uint32_t count; |
99 | 0 | rv = stream->Write(aData.Data(), aData.Length(), &count); |
100 | 0 | // Make sure we wrote correctly. |
101 | 0 | ASSERT_EQ(NS_SUCCEEDED(rv), true); |
102 | 0 | ASSERT_EQ(count, aData.Length()); |
103 | 0 |
|
104 | 0 | stream->Close(); |
105 | 0 | } |
106 | | |
107 | | void |
108 | | RemovePersistenceFile() |
109 | 0 | { |
110 | 0 | nsCOMPtr<nsIFile> file; |
111 | 0 | nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, |
112 | 0 | getter_AddRefs(file)); |
113 | 0 | ASSERT_EQ(NS_SUCCEEDED(rv), true); |
114 | 0 |
|
115 | 0 | // Append the filename and the extension. |
116 | 0 | nsAutoString fileName; |
117 | 0 | fileName.Append(kPersistedFilename); |
118 | 0 | file->Append(fileName); |
119 | 0 |
|
120 | 0 | bool exists = true; |
121 | 0 | rv = file->Exists(&exists); |
122 | 0 | ASSERT_EQ(NS_OK, rv) << "nsIFile::Exists cannot fail"; |
123 | 0 | |
124 | 0 | if (exists) { |
125 | 0 | rv = file->Remove(false); |
126 | 0 | ASSERT_EQ(NS_OK, rv) << "nsIFile::Remove cannot delete the requested file"; |
127 | 0 | } |
128 | 0 | } |
129 | | |
130 | | void |
131 | | CheckPersistenceFileExists(bool& aFileExists) |
132 | 0 | { |
133 | 0 | nsCOMPtr<nsIFile> file; |
134 | 0 | nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, |
135 | 0 | getter_AddRefs(file)); |
136 | 0 | ASSERT_EQ(NS_OK, rv) << "NS_GetSpecialDirectory must return a valid directory"; |
137 | 0 | |
138 | 0 | // Append the filename and the extension. |
139 | 0 | nsAutoString fileName; |
140 | 0 | fileName.Append(kPersistedFilename); |
141 | 0 | file->Append(fileName); |
142 | 0 |
|
143 | 0 | rv = file->Exists(&aFileExists); |
144 | 0 | ASSERT_EQ(NS_OK, rv) << "nsIFile::Exists must not fail"; |
145 | 0 | } |
146 | | |
147 | | /** |
148 | | * A helper class to wait for the internal "data loaded" |
149 | | * topic. |
150 | | */ |
151 | | class DataLoadedObserver final : public nsIObserver |
152 | | { |
153 | | ~DataLoadedObserver() = default; |
154 | | |
155 | | public: |
156 | | NS_DECL_ISUPPORTS |
157 | | |
158 | | explicit DataLoadedObserver() : |
159 | | mDataLoaded(false) |
160 | 0 | { |
161 | 0 | // The following line can fail to fetch the observer service. However, |
162 | 0 | // since we're test code, we're fine with crashing due to that. |
163 | 0 | nsCOMPtr<nsIObserverService> observerService = |
164 | 0 | mozilla::services::GetObserverService(); |
165 | 0 | observerService->AddObserver(this, kDataLoadedTopic, false); |
166 | 0 | } |
167 | | |
168 | | void WaitForNotification() |
169 | 0 | { |
170 | 0 | mozilla::SpinEventLoopUntil([&]() { return mDataLoaded; }); |
171 | 0 | } |
172 | | |
173 | | NS_IMETHOD Observe(nsISupports* aSubject, |
174 | | const char* aTopic, |
175 | | const char16_t* aData) override |
176 | 0 | { |
177 | 0 | if (!strcmp(aTopic, kDataLoadedTopic)) { |
178 | 0 | nsCOMPtr<nsIObserverService> observerService = |
179 | 0 | mozilla::services::GetObserverService(); |
180 | 0 | observerService->RemoveObserver(this, kDataLoadedTopic); |
181 | 0 | mDataLoaded = true; |
182 | 0 | } |
183 | 0 |
|
184 | 0 | return NS_OK; |
185 | 0 | } |
186 | | |
187 | | private: |
188 | | bool mDataLoaded; |
189 | | }; |
190 | | |
191 | | NS_IMPL_ISUPPORTS( |
192 | | DataLoadedObserver, |
193 | | nsIObserver |
194 | | ) |
195 | | |
196 | | } // Anonymous |
197 | | |
198 | | /** |
199 | | * A GeckoView specific test fixture. Please note that this |
200 | | * can't live in the above anonymous namespace. |
201 | | */ |
202 | | class TelemetryGeckoViewFixture : public TelemetryTestFixture { |
203 | | protected: |
204 | 0 | virtual void SetUp() { |
205 | 0 | TelemetryTestFixture::SetUp(); |
206 | 0 | MockAndroidDataDir(); |
207 | 0 | } |
208 | | }; |
209 | | |
210 | | namespace TelemetryGeckoViewTesting { |
211 | | |
212 | | void TestDispatchPersist(); |
213 | | |
214 | | } // TelemetryGeckoViewTesting |
215 | | |
216 | | /** |
217 | | * Test that corrupted JSON files don't crash the Telemetry core. |
218 | | */ |
219 | 0 | TEST_F(TelemetryGeckoViewFixture, CorruptedPersistenceFiles) { |
220 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
221 | 0 |
|
222 | 0 | // Try to load a corrupted file. |
223 | 0 | WritePersistenceFile(NS_LITERAL_CSTRING("{")); |
224 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
225 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
226 | 0 |
|
227 | 0 | // Cleanup/remove the files. |
228 | 0 | RemovePersistenceFile(); |
229 | 0 | } |
230 | | |
231 | | /** |
232 | | * Test that valid and empty JSON files don't crash the Telemetry core. |
233 | | */ |
234 | 0 | TEST_F(TelemetryGeckoViewFixture, EmptyPersistenceFiles) { |
235 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
236 | 0 |
|
237 | 0 | // Try to load an empty file/corrupted file. |
238 | 0 | WritePersistenceFile(EmptyCString()); |
239 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
240 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
241 | 0 |
|
242 | 0 | // Cleanup/remove the files. |
243 | 0 | RemovePersistenceFile(); |
244 | 0 | } |
245 | | |
246 | | /** |
247 | | * Test that we're able to clear the persistence storage. |
248 | | */ |
249 | 0 | TEST_F(TelemetryGeckoViewFixture, ClearPersistenceFiles) { |
250 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
251 | 0 |
|
252 | 0 | bool fileExists = false; |
253 | 0 | CheckPersistenceFileExists(fileExists); |
254 | 0 | ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk"; |
255 | 0 | |
256 | 0 | WritePersistenceFile(nsDependentCString(kSampleData)); |
257 | 0 | CheckPersistenceFileExists(fileExists); |
258 | 0 | ASSERT_TRUE(fileExists) << "We should have written the test persistence file to disk"; |
259 | 0 | |
260 | 0 | // Init the persistence: this will trigger the measurements to be written |
261 | 0 | // to disk off-the-main thread. |
262 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
263 | 0 | TelemetryGeckoViewPersistence::ClearPersistenceData(); |
264 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
265 | 0 |
|
266 | 0 | CheckPersistenceFileExists(fileExists); |
267 | 0 | ASSERT_FALSE(fileExists) << "ClearPersistenceData must remove the persistence file"; |
268 | 0 | } |
269 | | |
270 | | /** |
271 | | * Test that the data loaded topic gets notified correctly. |
272 | | */ |
273 | 0 | TEST_F(TelemetryGeckoViewFixture, CheckDataLoadedTopic) { |
274 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
275 | 0 |
|
276 | 0 | bool fileExists = false; |
277 | 0 | CheckPersistenceFileExists(fileExists); |
278 | 0 | ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk"; |
279 | 0 | |
280 | 0 | // Check that the data loaded topic is notified after attempting the load |
281 | 0 | // if no measurement file exists. |
282 | 0 | RefPtr<DataLoadedObserver> loadingFinished = new DataLoadedObserver(); |
283 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
284 | 0 | loadingFinished->WaitForNotification(); |
285 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
286 | 0 |
|
287 | 0 | // Check that the topic is triggered when the measuements file exists. |
288 | 0 | WritePersistenceFile(nsDependentCString(kSampleData)); |
289 | 0 | CheckPersistenceFileExists(fileExists); |
290 | 0 | ASSERT_TRUE(fileExists) << "The persisted measurements must exist on the disk"; |
291 | 0 | |
292 | 0 | // Check that the data loaded topic is triggered when the measurement file exists. |
293 | 0 | loadingFinished = new DataLoadedObserver(); |
294 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
295 | 0 | loadingFinished->WaitForNotification(); |
296 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
297 | 0 |
|
298 | 0 | // Cleanup/remove the files. |
299 | 0 | RemovePersistenceFile(); |
300 | 0 | } |
301 | | |
302 | | /** |
303 | | * Test that we can correctly persist the scalar data. |
304 | | */ |
305 | 0 | TEST_F(TelemetryGeckoViewFixture, PersistScalars) { |
306 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
307 | 0 |
|
308 | 0 | Unused << mTelemetry->ClearScalars(); |
309 | 0 |
|
310 | 0 | bool fileExists = false; |
311 | 0 | CheckPersistenceFileExists(fileExists); |
312 | 0 | ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk"; |
313 | 0 | |
314 | 0 | RefPtr<DataLoadedObserver> loadingFinished = new DataLoadedObserver(); |
315 | 0 |
|
316 | 0 | // Init the persistence: this will trigger the measurements to be written |
317 | 0 | // to disk off-the-main thread. |
318 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
319 | 0 | loadingFinished->WaitForNotification(); |
320 | 0 |
|
321 | 0 | // Set some scalars: we can only test the parent process as we don't support other |
322 | 0 | // processes in gtests. |
323 | 0 | const uint32_t kExpectedUintValue = 37; |
324 | 0 | const uint32_t kExpectedKeyedUintValue = 73; |
325 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_ALL_PROCESSES_UINT, kExpectedUintValue); |
326 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, |
327 | 0 | NS_LITERAL_STRING("gv_key"), kExpectedKeyedUintValue); |
328 | 0 |
|
329 | 0 | // Dispatch the persisting task: we don't wait for the timer to expire |
330 | 0 | // as we need a reliable and reproducible way to kick this off. We ensure |
331 | 0 | // that the task runs by shutting down the persistence: this shuts down the |
332 | 0 | // thread which executes the task as the last action. |
333 | 0 | TelemetryGeckoViewTesting::TestDispatchPersist(); |
334 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
335 | 0 |
|
336 | 0 | CheckPersistenceFileExists(fileExists); |
337 | 0 | ASSERT_TRUE(fileExists) << "The persisted measurements must exist on the disk"; |
338 | 0 | |
339 | 0 | // Clear the in-memory scalars again. They will be restored from the disk. |
340 | 0 | Unused << mTelemetry->ClearScalars(); |
341 | 0 |
|
342 | 0 | // Load the persisted file again. |
343 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
344 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
345 | 0 |
|
346 | 0 | // Get a snapshot of the keyed and plain scalars. |
347 | 0 | JS::RootedValue scalarsSnapshot(cx.GetJSContext()); |
348 | 0 | JS::RootedValue keyedScalarsSnapshot(cx.GetJSContext()); |
349 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
350 | 0 | GetScalarsSnapshot(true, cx.GetJSContext(), &keyedScalarsSnapshot); |
351 | 0 |
|
352 | 0 | // Verify that the scalars were correctly persisted and restored. |
353 | 0 | CheckUintScalar("telemetry.test.all_processes_uint", cx.GetJSContext(), |
354 | 0 | scalarsSnapshot, kExpectedUintValue); |
355 | 0 | CheckKeyedUintScalar("telemetry.test.keyed_unsigned_int", "gv_key", cx.GetJSContext(), |
356 | 0 | keyedScalarsSnapshot, kExpectedKeyedUintValue); |
357 | 0 |
|
358 | 0 | // Cleanup/remove the files. |
359 | 0 | RemovePersistenceFile(); |
360 | 0 | } |
361 | | |
362 | | /** |
363 | | * Test that we can correctly persist the histogram data. |
364 | | */ |
365 | 0 | TEST_F(TelemetryGeckoViewFixture, PersistHistograms) { |
366 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
367 | 0 |
|
368 | 0 | // Clear the histogram data. |
369 | 0 | GetAndClearHistogram(cx.GetJSContext(), mTelemetry, |
370 | 0 | NS_LITERAL_CSTRING("TELEMETRY_TEST_MULTIPRODUCT"), false /* is_keyed */); |
371 | 0 | GetAndClearHistogram(cx.GetJSContext(), mTelemetry, |
372 | 0 | NS_LITERAL_CSTRING("TELEMETRY_TEST_KEYED_COUNT"), true /* is_keyed */); |
373 | 0 |
|
374 | 0 | bool fileExists = false; |
375 | 0 | CheckPersistenceFileExists(fileExists); |
376 | 0 | ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk"; |
377 | 0 | |
378 | 0 | RefPtr<DataLoadedObserver> loadingFinished = new DataLoadedObserver(); |
379 | 0 |
|
380 | 0 | // Init the persistence: this will trigger the measurements to be written |
381 | 0 | // to disk off-the-main thread. |
382 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
383 | 0 | loadingFinished->WaitForNotification(); |
384 | 0 |
|
385 | 0 | // Set some histograms: we can only test the parent process as we don't support other |
386 | 0 | // processes in gtests. |
387 | 0 | const uint32_t kExpectedUintValue = 37; |
388 | 0 | const nsTArray<uint32_t> keyedSamples({5, 10, 15}); |
389 | 0 | const uint32_t kExpectedKeyedSum = 5 + 10 + 15; |
390 | 0 | Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_MULTIPRODUCT, kExpectedUintValue); |
391 | 0 | Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_COUNT, NS_LITERAL_CSTRING("gv_key"), |
392 | 0 | keyedSamples); |
393 | 0 |
|
394 | 0 | // Dispatch the persisting task: we don't wait for the timer to expire |
395 | 0 | // as we need a reliable and reproducible way to kick off this. We ensure |
396 | 0 | // that the task runs by shutting down the persistence: this shuts down the |
397 | 0 | // thread which executes the task as the last action. |
398 | 0 | TelemetryGeckoViewTesting::TestDispatchPersist(); |
399 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
400 | 0 |
|
401 | 0 | CheckPersistenceFileExists(fileExists); |
402 | 0 | ASSERT_TRUE(fileExists) << "The persisted measurements must exist on the disk"; |
403 | 0 | |
404 | 0 | // Clear the in-memory histograms again. They will be restored from the disk. |
405 | 0 | GetAndClearHistogram(cx.GetJSContext(), mTelemetry, |
406 | 0 | NS_LITERAL_CSTRING("TELEMETRY_TEST_MULTIPRODUCT"), false /* is_keyed */); |
407 | 0 | GetAndClearHistogram(cx.GetJSContext(), mTelemetry, |
408 | 0 | NS_LITERAL_CSTRING("TELEMETRY_TEST_KEYED_COUNT"), true /* is_keyed */); |
409 | 0 |
|
410 | 0 |
|
411 | 0 | // Load the persisted file again. |
412 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
413 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
414 | 0 |
|
415 | 0 | // Get a snapshot of the keyed and plain histograms. |
416 | 0 | JS::RootedValue snapshot(cx.GetJSContext()); |
417 | 0 | JS::RootedValue keyedSnapshot(cx.GetJSContext()); |
418 | 0 | GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_MULTIPRODUCT", |
419 | 0 | &snapshot, false /* is_keyed */); |
420 | 0 | GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT", |
421 | 0 | &keyedSnapshot, true /* is_keyed */); |
422 | 0 |
|
423 | 0 | // Validate the loaded histogram data. |
424 | 0 | JS::RootedValue histogram(cx.GetJSContext()); |
425 | 0 | GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_MULTIPRODUCT", snapshot, &histogram); |
426 | 0 |
|
427 | 0 | // Get "sum" property from histogram |
428 | 0 | JS::RootedValue sum(cx.GetJSContext()); |
429 | 0 | GetProperty(cx.GetJSContext(), "sum", histogram, &sum); |
430 | 0 |
|
431 | 0 | // Check that the "sum" stored in the histogram matches with |kExpectedValue| |
432 | 0 | uint32_t uSum = 0; |
433 | 0 | JS::ToUint32(cx.GetJSContext(), sum, &uSum); |
434 | 0 | ASSERT_EQ(uSum, kExpectedUintValue) << "The histogram is not returning the expected value"; |
435 | 0 | |
436 | 0 | // Validate the keyed histogram data. |
437 | 0 | GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", keyedSnapshot, &histogram); |
438 | 0 |
|
439 | 0 | // Get "testkey" property from histogram and check that it stores the correct |
440 | 0 | // data. |
441 | 0 | JS::RootedValue expectedKeyData(cx.GetJSContext()); |
442 | 0 | GetProperty(cx.GetJSContext(), "gv_key", histogram, &expectedKeyData); |
443 | 0 | ASSERT_FALSE(expectedKeyData.isUndefined()) |
444 | 0 | << "Cannot find the expected key in the keyed histogram data"; |
445 | 0 | GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum); |
446 | 0 | JS::ToUint32(cx.GetJSContext(), sum, &uSum); |
447 | 0 | ASSERT_EQ(uSum, kExpectedKeyedSum) |
448 | 0 | << "The histogram is not returning the expected sum for 'gv_key'"; |
449 | 0 | |
450 | 0 | // Cleanup/remove the files. |
451 | 0 | RemovePersistenceFile(); |
452 | 0 | } |
453 | | |
454 | | /** |
455 | | * Test GeckoView timer telemetry is correctly recorded. |
456 | | */ |
457 | 0 | TEST_F(TelemetryGeckoViewFixture, TimerHitCountProbe) { |
458 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
459 | 0 |
|
460 | 0 | Unused << mTelemetry->ClearScalars(); |
461 | 0 |
|
462 | 0 | // Init the persistence and wait for loading to complete. |
463 | 0 | RefPtr<DataLoadedObserver> loadingFinished = new DataLoadedObserver(); |
464 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
465 | 0 | loadingFinished->WaitForNotification(); |
466 | 0 | // Simulate hitting the timer twice. |
467 | 0 | TelemetryGeckoViewTesting::TestDispatchPersist(); |
468 | 0 | TelemetryGeckoViewTesting::TestDispatchPersist(); |
469 | 0 | TelemetryGeckoViewPersistence::DeInitPersistence(); |
470 | 0 |
|
471 | 0 | // Get a snapshot of the keyed and plain scalars. |
472 | 0 | JS::RootedValue scalarsSnapshot(cx.GetJSContext()); |
473 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
474 | 0 |
|
475 | 0 | // Verify that the scalars were correctly persisted and restored. |
476 | 0 | CheckUintScalar("telemetry.persistence_timer_hit_count", cx.GetJSContext(), |
477 | 0 | scalarsSnapshot, 2); |
478 | 0 |
|
479 | 0 | // Cleanup/remove the files. |
480 | 0 | RemovePersistenceFile(); |
481 | 0 | } |
482 | | |
483 | 0 | TEST_F(TelemetryGeckoViewFixture, EmptyPendingOperations) { |
484 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
485 | 0 |
|
486 | 0 | Unused << mTelemetry->ClearScalars(); |
487 | 0 |
|
488 | 0 | // Force loading mode |
489 | 0 | TelemetryScalar::DeserializationStarted(); |
490 | 0 |
|
491 | 0 | // Do nothing explicitely |
492 | 0 |
|
493 | 0 | // Force pending operations to be applied and end load mode. |
494 | 0 | // It should not crash and don't change any scalars. |
495 | 0 | TelemetryScalar::ApplyPendingOperations(); |
496 | 0 |
|
497 | 0 | // Check that the snapshot is empty |
498 | 0 | JS::RootedValue scalarsSnapshot(cx.GetJSContext()); |
499 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
500 | 0 |
|
501 | 0 | ASSERT_TRUE(scalarsSnapshot.isUndefined()) << "Scalars snapshot should not contain any data."; |
502 | 0 | } |
503 | | |
504 | 0 | TEST_F(TelemetryGeckoViewFixture, SimpleAppendOperation) { |
505 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
506 | 0 |
|
507 | 0 | Unused << mTelemetry->ClearScalars(); |
508 | 0 |
|
509 | 0 | // Set an initial value, so we can test that it is not overwritten. |
510 | 0 | uint32_t initialValue = 1; |
511 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, initialValue); |
512 | 0 |
|
513 | 0 | // Force loading mode |
514 | 0 | TelemetryScalar::DeserializationStarted(); |
515 | 0 |
|
516 | 0 | // Add to a scalar |
517 | 0 | uint32_t value = 37; |
518 | 0 | Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, value); |
519 | 0 |
|
520 | 0 | // Verify that this was not yet applied. |
521 | 0 | JS::RootedValue scalarsSnapshot(cx.GetJSContext()); |
522 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
523 | 0 |
|
524 | 0 | CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), scalarsSnapshot, initialValue); |
525 | 0 |
|
526 | 0 | // Force pending operations to be applied and end load mode |
527 | 0 | TelemetryScalar::ApplyPendingOperations(); |
528 | 0 |
|
529 | 0 | // Verify recorded operations are applied |
530 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
531 | 0 | CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), scalarsSnapshot, initialValue+value); |
532 | 0 | } |
533 | | |
534 | 0 | TEST_F(TelemetryGeckoViewFixture, ApplyPendingOperationsAfterLoad) { |
535 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
536 | 0 |
|
537 | 0 | Unused << mTelemetry->ClearScalars(); |
538 | 0 |
|
539 | 0 | const char persistedData[] = R"({ |
540 | 0 | "scalars": { |
541 | 0 | "parent": { |
542 | 0 | "telemetry.test.unsigned_int_kind": 14 |
543 | 0 | } |
544 | 0 | } |
545 | 0 | })"; |
546 | 0 |
|
547 | 0 | // Force loading mode |
548 | 0 | TelemetryScalar::DeserializationStarted(); |
549 | 0 |
|
550 | 0 | // Add to a scalar, this should be recorded |
551 | 0 | uint32_t addValue = 10; |
552 | 0 | Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, addValue); |
553 | 0 |
|
554 | 0 | // Load persistence file |
555 | 0 | RefPtr<DataLoadedObserver> loadingFinished = new DataLoadedObserver(); |
556 | 0 | WritePersistenceFile(nsDependentCString(persistedData)); |
557 | 0 | TelemetryGeckoViewPersistence::InitPersistence(); |
558 | 0 | loadingFinished->WaitForNotification(); |
559 | 0 |
|
560 | 0 | // At this point all pending operations should have been applied. |
561 | 0 |
|
562 | 0 | // Increment again, now directly applied |
563 | 0 | uint32_t val = 1; |
564 | 0 | Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, val); |
565 | 0 |
|
566 | 0 |
|
567 | 0 | JS::RootedValue scalarsSnapshot(cx.GetJSContext()); |
568 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
569 | 0 |
|
570 | 0 | uint32_t expectedValue = 25; |
571 | 0 | CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), scalarsSnapshot, expectedValue); |
572 | 0 | } |
573 | | |
574 | 0 | TEST_F(TelemetryGeckoViewFixture, MultipleAppendOperations) { |
575 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
576 | 0 |
|
577 | 0 | Unused << mTelemetry->ClearScalars(); |
578 | 0 |
|
579 | 0 | // Force loading mode |
580 | 0 | TelemetryScalar::DeserializationStarted(); |
581 | 0 |
|
582 | 0 | // Modify all kinds of scalars |
583 | 0 | uint32_t startValue = 35; |
584 | 0 | uint32_t expectedValue = 40; |
585 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, startValue); |
586 | 0 | Telemetry::ScalarSetMaximum(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, startValue + 2); |
587 | 0 | Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, 3); |
588 | 0 |
|
589 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, true); |
590 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, NS_LITERAL_STRING("Star Wars VI")); |
591 | 0 |
|
592 | 0 | // Modify all kinds of keyed scalars |
593 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, |
594 | 0 | NS_LITERAL_STRING("chewbacca"), startValue); |
595 | 0 | Telemetry::ScalarSetMaximum(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, |
596 | 0 | NS_LITERAL_STRING("chewbacca"), startValue + 2); |
597 | 0 | Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, |
598 | 0 | NS_LITERAL_STRING("chewbacca"), 3); |
599 | 0 |
|
600 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, |
601 | 0 | NS_LITERAL_STRING("chewbacca"), |
602 | 0 | true); |
603 | 0 |
|
604 | 0 | // Force pending operations to be applied and end load mode |
605 | 0 | TelemetryScalar::ApplyPendingOperations(); |
606 | 0 |
|
607 | 0 | JS::RootedValue scalarsSnapshot(cx.GetJSContext()); |
608 | 0 | JS::RootedValue keyedScalarsSnapshot(cx.GetJSContext()); |
609 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
610 | 0 | GetScalarsSnapshot(true, cx.GetJSContext(), &keyedScalarsSnapshot); |
611 | 0 |
|
612 | 0 | CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(), scalarsSnapshot, expectedValue); |
613 | 0 | CheckBoolScalar("telemetry.test.boolean_kind", cx.GetJSContext(), scalarsSnapshot, true); |
614 | 0 | CheckStringScalar("telemetry.test.string_kind", cx.GetJSContext(), scalarsSnapshot, "Star Wars VI"); |
615 | 0 |
|
616 | 0 | CheckKeyedUintScalar("telemetry.test.keyed_unsigned_int", "chewbacca", |
617 | 0 | cx.GetJSContext(), keyedScalarsSnapshot, expectedValue); |
618 | 0 | CheckKeyedBoolScalar("telemetry.test.keyed_boolean_kind", "chewbacca", |
619 | 0 | cx.GetJSContext(), keyedScalarsSnapshot, true); |
620 | 0 | } |
621 | | |
622 | 0 | TEST_F(TelemetryGeckoViewFixture, PendingOperationsHighWater) { |
623 | 0 | AutoJSContextWithGlobal cx(mCleanGlobal); |
624 | 0 |
|
625 | 0 | const char* testProbeName = "telemetry.test.unsigned_int_kind"; |
626 | 0 | const char* reachedName = "telemetry.pending_operations_highwatermark_reached"; |
627 | 0 |
|
628 | 0 | Unused << mTelemetry->ClearScalars(); |
629 | 0 |
|
630 | 0 | // Setting initial values so we can test them easily |
631 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, 0u); |
632 | 0 | Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_PENDING_OPERATIONS_HIGHWATERMARK_REACHED, 0u); |
633 | 0 |
|
634 | 0 | // Force loading mode |
635 | 0 | TelemetryScalar::DeserializationStarted(); |
636 | 0 |
|
637 | 0 | // Fill up the pending operations list |
638 | 0 | uint32_t expectedValue = 10000; |
639 | 0 | for (uint32_t i=0; i < expectedValue; i++) { |
640 | 0 | Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, 1); |
641 | 0 | } |
642 | 0 |
|
643 | 0 | // Nothing should be applied yet |
644 | 0 | JS::RootedValue scalarsSnapshot(cx.GetJSContext()); |
645 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
646 | 0 | CheckUintScalar(testProbeName, cx.GetJSContext(), scalarsSnapshot, 0); |
647 | 0 | CheckUintScalar(reachedName, cx.GetJSContext(), scalarsSnapshot, 0); |
648 | 0 |
|
649 | 0 | // Spill over the buffer to immediately apply all operations |
650 | 0 | Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, 1); |
651 | 0 |
|
652 | 0 | // Now we should see all values |
653 | 0 | GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot); |
654 | 0 | CheckUintScalar(testProbeName, cx.GetJSContext(), scalarsSnapshot, expectedValue+1); |
655 | 0 | CheckUintScalar(reachedName, cx.GetJSContext(), scalarsSnapshot, 1); |
656 | 0 | } |