Coverage Report

Created: 2018-09-25 14:53

/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
}