1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/
3 */
4
5 #include "TelemetryTestHelpers.h"
6
7 #include "core/TelemetryCommon.h"
8 #include "core/TelemetryOrigin.h"
9 #include "gtest/gtest.h"
10 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
11 #include "mozilla/CycleCollectedJSContext.h"
12 #include "mozilla/Unused.h"
13 #include "nsPrintfCString.h"
14
15 using namespace mozilla;
16
17 // Helper methods provided to simplify writing tests and meant to be used in C++
18 // Gtests.
19 namespace TelemetryTestHelpers {
20
CheckUintScalar(const char * aName,JSContext * aCx,JS::HandleValue aSnapshot,uint32_t expectedValue)21 void CheckUintScalar(const char* aName, JSContext* aCx,
22 JS::HandleValue aSnapshot, uint32_t expectedValue) {
23 // Validate the value of the test scalar.
24 JS::RootedValue value(aCx);
25 JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
26 ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value))
27 << "The test scalar must be reported.";
28 JS_GetProperty(aCx, scalarObj, aName, &value);
29
30 ASSERT_TRUE(value.isInt32())
31 << "The scalar value must be of the correct type.";
32 ASSERT_TRUE(value.toInt32() >= 0)
33 << "The uint scalar type must contain a value >= 0.";
34 ASSERT_EQ(static_cast<uint32_t>(value.toInt32()), expectedValue)
35 << "The scalar value must match the expected value.";
36 }
37
CheckBoolScalar(const char * aName,JSContext * aCx,JS::HandleValue aSnapshot,bool expectedValue)38 void CheckBoolScalar(const char* aName, JSContext* aCx,
39 JS::HandleValue aSnapshot, bool expectedValue) {
40 // Validate the value of the test scalar.
41 JS::RootedValue value(aCx);
42 JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
43 ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value))
44 << "The test scalar must be reported.";
45 ASSERT_TRUE(value.isBoolean())
46 << "The scalar value must be of the correct type.";
47 ASSERT_EQ(static_cast<bool>(value.toBoolean()), expectedValue)
48 << "The scalar value must match the expected value.";
49 }
50
CheckStringScalar(const char * aName,JSContext * aCx,JS::HandleValue aSnapshot,const char * expectedValue)51 void CheckStringScalar(const char* aName, JSContext* aCx,
52 JS::HandleValue aSnapshot, const char* expectedValue) {
53 // Validate the value of the test scalar.
54 JS::RootedValue value(aCx);
55 JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
56 ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value))
57 << "The test scalar must be reported.";
58 ASSERT_TRUE(value.isString())
59 << "The scalar value must be of the correct type.";
60
61 bool sameString;
62 ASSERT_TRUE(
63 JS_StringEqualsAscii(aCx, value.toString(), expectedValue, &sameString))
64 << "JS String comparison failed";
65 ASSERT_TRUE(sameString)
66 << "The scalar value must match the expected string";
67 }
68
CheckKeyedUintScalar(const char * aName,const char * aKey,JSContext * aCx,JS::HandleValue aSnapshot,uint32_t expectedValue)69 void CheckKeyedUintScalar(const char* aName, const char* aKey, JSContext* aCx,
70 JS::HandleValue aSnapshot, uint32_t expectedValue) {
71 JS::RootedValue keyedScalar(aCx);
72 JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
73 // Get the aName keyed scalar object from the scalars snapshot.
74 ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar))
75 << "The keyed scalar must be reported.";
76
77 CheckUintScalar(aKey, aCx, keyedScalar, expectedValue);
78 }
79
CheckKeyedBoolScalar(const char * aName,const char * aKey,JSContext * aCx,JS::HandleValue aSnapshot,bool expectedValue)80 void CheckKeyedBoolScalar(const char* aName, const char* aKey, JSContext* aCx,
81 JS::HandleValue aSnapshot, bool expectedValue) {
82 JS::RootedValue keyedScalar(aCx);
83 JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
84 // Get the aName keyed scalar object from the scalars snapshot.
85 ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar))
86 << "The keyed scalar must be reported.";
87
88 CheckBoolScalar(aKey, aCx, keyedScalar, expectedValue);
89 }
90
CheckNumberOfProperties(const char * aName,JSContext * aCx,JS::HandleValue aSnapshot,uint32_t expectedNumProperties)91 void CheckNumberOfProperties(const char* aName, JSContext* aCx,
92 JS::HandleValue aSnapshot,
93 uint32_t expectedNumProperties) {
94 JS::RootedValue keyedScalar(aCx);
95 JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
96 // Get the aName keyed scalar object from the scalars snapshot.
97 ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar))
98 << "The keyed scalar must be reported.";
99
100 JS::RootedObject keyedScalarObj(aCx, &keyedScalar.toObject());
101 JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
102 ASSERT_TRUE(JS_Enumerate(aCx, keyedScalarObj, &ids))
103 << "We must be able to get keyed scalar members.";
104
105 ASSERT_EQ(expectedNumProperties, ids.length())
106 << "The scalar must report the expected number of properties.";
107 }
108
EventPresent(JSContext * aCx,const JS::RootedValue & aSnapshot,const nsACString & aCategory,const nsACString & aMethod,const nsACString & aObject)109 bool EventPresent(JSContext* aCx, const JS::RootedValue& aSnapshot,
110 const nsACString& aCategory, const nsACString& aMethod,
111 const nsACString& aObject) {
112 EXPECT_FALSE(aSnapshot.isNullOrUndefined())
113 << "Event snapshot must not be null/undefined.";
114 bool isArray = false;
115 EXPECT_TRUE(JS::IsArrayObject(aCx, aSnapshot, &isArray) && isArray)
116 << "The snapshot must be an array.";
117 JS::RootedObject arrayObj(aCx, &aSnapshot.toObject());
118 uint32_t arrayLength = 0;
119 EXPECT_TRUE(JS::GetArrayLength(aCx, arrayObj, &arrayLength))
120 << "Array must have a length.";
121 EXPECT_TRUE(arrayLength > 0) << "Array must have at least one element.";
122
123 for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; ++arrayIdx) {
124 JS::Rooted<JS::Value> element(aCx);
125 EXPECT_TRUE(JS_GetElement(aCx, arrayObj, arrayIdx, &element))
126 << "Must be able to get element.";
127 EXPECT_TRUE(JS::IsArrayObject(aCx, element, &isArray) && isArray)
128 << "Element must be an array.";
129 JS::RootedObject eventArray(aCx, &element.toObject());
130 uint32_t eventLength;
131 EXPECT_TRUE(JS::GetArrayLength(aCx, eventArray, &eventLength))
132 << "Event array must have a length.";
133 EXPECT_TRUE(eventLength >= 4)
134 << "Event array must have at least 4 elements (timestamp, category, "
135 "method, object).";
136
137 JS::Rooted<JS::Value> str(aCx);
138 nsAutoJSString jsStr;
139 EXPECT_TRUE(JS_GetElement(aCx, eventArray, 1, &str))
140 << "Must be able to get category.";
141 EXPECT_TRUE(str.isString()) << "Category must be a string.";
142 EXPECT_TRUE(jsStr.init(aCx, str))
143 << "Category must be able to be init'd to a jsstring.";
144 if (NS_ConvertUTF16toUTF8(jsStr) != aCategory) {
145 continue;
146 }
147
148 EXPECT_TRUE(JS_GetElement(aCx, eventArray, 2, &str))
149 << "Must be able to get method.";
150 EXPECT_TRUE(str.isString()) << "Method must be a string.";
151 EXPECT_TRUE(jsStr.init(aCx, str))
152 << "Method must be able to be init'd to a jsstring.";
153 if (NS_ConvertUTF16toUTF8(jsStr) != aMethod) {
154 continue;
155 }
156
157 EXPECT_TRUE(JS_GetElement(aCx, eventArray, 3, &str))
158 << "Must be able to get object.";
159 EXPECT_TRUE(str.isString()) << "Object must be a string.";
160 EXPECT_TRUE(jsStr.init(aCx, str))
161 << "Object must be able to be init'd to a jsstring.";
162 if (NS_ConvertUTF16toUTF8(jsStr) != aObject) {
163 continue;
164 }
165
166 // We found it!
167 return true;
168 }
169
170 // We didn't find it!
171 return false;
172 }
173
GetOriginSnapshot(JSContext * aCx,JS::MutableHandle<JS::Value> aResult,bool aClear)174 void GetOriginSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
175 bool aClear) {
176 nsCOMPtr<nsITelemetry> telemetry =
177 do_GetService("@mozilla.org/base/telemetry;1");
178
179 JS::RootedValue originSnapshot(aCx);
180 nsresult rv;
181 rv = telemetry->GetOriginSnapshot(aClear, aCx, &originSnapshot);
182 ASSERT_EQ(rv, NS_OK) << "Snapshotting origin data must not fail.";
183 ASSERT_TRUE(originSnapshot.isObject())
184 << "The snapshot must be an object.";
185
186 aResult.set(originSnapshot);
187 }
188
189 /*
190 * Extracts the `a` and `b` strings from the prioData snapshot object
191 * of any length. Which looks like:
192 *
193 * [{
194 * encoding: encodingName,
195 * prio: {
196 * a: <string>,
197 * b: <string>,
198 * },
199 * }, ...]
200 */
GetEncodedOriginStrings(JSContext * aCx,const nsCString & aEncoding,nsTArray<Tuple<nsCString,nsCString>> & aPrioStrings)201 void GetEncodedOriginStrings(
202 JSContext* aCx, const nsCString& aEncoding,
203 nsTArray<Tuple<nsCString, nsCString>>& aPrioStrings) {
204 JS::RootedValue snapshot(aCx);
205 nsresult rv;
206 rv = TelemetryOrigin::GetEncodedOriginSnapshot(false /* clear */, aCx,
207 &snapshot);
208
209 ASSERT_FALSE(NS_FAILED(rv));
210 ASSERT_FALSE(snapshot.isNullOrUndefined())
211 << "Encoded snapshot must not be null/undefined.";
212
213 JS::RootedObject prioDataObj(aCx, &snapshot.toObject());
214 bool isArray = false;
215 ASSERT_TRUE(JS::IsArrayObject(aCx, prioDataObj, &isArray) && isArray)
216 << "The metric's origins must be in an array.";
217
218 uint32_t length = 0;
219 ASSERT_TRUE(JS::GetArrayLength(aCx, prioDataObj, &length));
220 ASSERT_TRUE(length > 0)
221 << "Length of returned array must greater than 0";
222
223 for (auto i = 0u; i < length; ++i) {
224 JS::RootedValue arrayItem(aCx);
225 ASSERT_TRUE(JS_GetElement(aCx, prioDataObj, i, &arrayItem));
226 ASSERT_TRUE(arrayItem.isObject());
227 ASSERT_FALSE(arrayItem.isNullOrUndefined());
228
229 JS::RootedObject arrayItemObj(aCx, &arrayItem.toObject());
230
231 JS::RootedValue encodingVal(aCx);
232 ASSERT_TRUE(JS_GetProperty(aCx, arrayItemObj, "encoding", &encodingVal));
233 ASSERT_TRUE(encodingVal.isString());
234 nsAutoJSString jsStr;
235 ASSERT_TRUE(jsStr.init(aCx, encodingVal));
236
237 nsPrintfCString encoding(aEncoding.get(),
238 i % TelemetryOrigin::SizeOfPrioDatasPerMetric());
239 ASSERT_TRUE(NS_ConvertUTF16toUTF8(jsStr) == encoding)
240 << "Actual 'encoding' (" << NS_ConvertUTF16toUTF8(jsStr).get()
241 << ") must match expected (" << encoding << ")";
242
243 JS::RootedValue prioVal(aCx);
244 ASSERT_TRUE(JS_GetProperty(aCx, arrayItemObj, "prio", &prioVal));
245 ASSERT_TRUE(prioVal.isObject());
246 ASSERT_FALSE(prioVal.isNullOrUndefined());
247
248 JS::RootedObject prioObj(aCx, &prioVal.toObject());
249
250 JS::RootedValue aVal(aCx);
251 nsAutoJSString aStr;
252 ASSERT_TRUE(JS_GetProperty(aCx, prioObj, "a", &aVal));
253 ASSERT_TRUE(aVal.isString());
254 ASSERT_TRUE(aStr.init(aCx, aVal));
255
256 JS::RootedValue bVal(aCx);
257 nsAutoJSString bStr;
258 ASSERT_TRUE(JS_GetProperty(aCx, prioObj, "b", &bVal));
259 ASSERT_TRUE(bVal.isString());
260 ASSERT_TRUE(bStr.init(aCx, bVal));
261
262 aPrioStrings.AppendElement(Tuple<nsCString, nsCString>(
263 NS_ConvertUTF16toUTF8(aStr), NS_ConvertUTF16toUTF8(bStr)));
264 }
265 }
266
GetEventSnapshot(JSContext * aCx,JS::MutableHandle<JS::Value> aResult,ProcessID aProcessType)267 void GetEventSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
268 ProcessID aProcessType) {
269 nsCOMPtr<nsITelemetry> telemetry =
270 do_GetService("@mozilla.org/base/telemetry;1");
271
272 JS::RootedValue eventSnapshot(aCx);
273 nsresult rv;
274 rv = telemetry->SnapshotEvents(1 /* PRERELEASE_CHANNELS */, false /* clear */,
275 0 /* eventLimit */, aCx, 1 /* argc */,
276 &eventSnapshot);
277 ASSERT_EQ(rv, NS_OK) << "Snapshotting events must not fail.";
278 ASSERT_TRUE(eventSnapshot.isObject())
279 << "The snapshot must be an object.";
280
281 JS::RootedValue processEvents(aCx);
282 JS::RootedObject eventObj(aCx, &eventSnapshot.toObject());
283 Unused << JS_GetProperty(aCx, eventObj,
284 Telemetry::Common::GetNameForProcessID(aProcessType),
285 &processEvents);
286
287 aResult.set(processEvents);
288 }
289
GetScalarsSnapshot(bool aKeyed,JSContext * aCx,JS::MutableHandle<JS::Value> aResult,ProcessID aProcessType)290 void GetScalarsSnapshot(bool aKeyed, JSContext* aCx,
291 JS::MutableHandle<JS::Value> aResult,
292 ProcessID aProcessType) {
293 nsCOMPtr<nsITelemetry> telemetry =
294 do_GetService("@mozilla.org/base/telemetry;1");
295
296 // Get a snapshot of the scalars.
297 JS::RootedValue scalarsSnapshot(aCx);
298 nsresult rv;
299
300 if (aKeyed) {
301 rv = telemetry->GetSnapshotForKeyedScalars(NS_LITERAL_CSTRING("main"),
302 false, false /* filter */, aCx,
303 &scalarsSnapshot);
304 } else {
305 rv = telemetry->GetSnapshotForScalars(NS_LITERAL_CSTRING("main"), false,
306 false /* filter */, aCx,
307 &scalarsSnapshot);
308 }
309
310 // Validate the snapshot.
311 ASSERT_EQ(rv, NS_OK) << "Creating a snapshot of the data must not fail.";
312 ASSERT_TRUE(scalarsSnapshot.isObject())
313 << "The snapshot must be an object.";
314
315 JS::RootedValue processScalars(aCx);
316 JS::RootedObject scalarObj(aCx, &scalarsSnapshot.toObject());
317 // Don't complain if no scalars for the process can be found. Just
318 // return an empty object.
319 Unused << JS_GetProperty(aCx, scalarObj,
320 Telemetry::Common::GetNameForProcessID(aProcessType),
321 &processScalars);
322
323 aResult.set(processScalars);
324 }
325
GetAndClearHistogram(JSContext * cx,nsCOMPtr<nsITelemetry> mTelemetry,const nsACString & name,bool is_keyed)326 void GetAndClearHistogram(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry,
327 const nsACString& name, bool is_keyed) {
328 JS::RootedValue testHistogram(cx);
329 nsresult rv =
330 is_keyed ? mTelemetry->GetKeyedHistogramById(name, cx, &testHistogram)
331 : mTelemetry->GetHistogramById(name, cx, &testHistogram);
332
333 ASSERT_EQ(rv, NS_OK) << "Cannot fetch histogram";
334
335 // Clear the stored value
336 JS::RootedObject testHistogramObj(cx, &testHistogram.toObject());
337 JS::RootedValue rval(cx);
338 ASSERT_TRUE(JS_CallFunctionName(cx, testHistogramObj, "clear",
339 JS::HandleValueArray::empty(), &rval))
340 << "Cannot clear histogram";
341 }
342
GetProperty(JSContext * cx,const char * name,JS::HandleValue valueIn,JS::MutableHandleValue valueOut)343 void GetProperty(JSContext* cx, const char* name, JS::HandleValue valueIn,
344 JS::MutableHandleValue valueOut) {
345 JS::RootedValue property(cx);
346 JS::RootedObject valueInObj(cx, &valueIn.toObject());
347 ASSERT_TRUE(JS_GetProperty(cx, valueInObj, name, &property))
348 << "Cannot get property '" << name << "'";
349 valueOut.set(property);
350 }
351
GetElement(JSContext * cx,uint32_t index,JS::HandleValue valueIn,JS::MutableHandleValue valueOut)352 void GetElement(JSContext* cx, uint32_t index, JS::HandleValue valueIn,
353 JS::MutableHandleValue valueOut) {
354 JS::RootedValue element(cx);
355 JS::RootedObject valueInObj(cx, &valueIn.toObject());
356 ASSERT_TRUE(JS_GetElement(cx, valueInObj, index, &element))
357 << "Cannot get element at index '" << index << "'";
358 valueOut.set(element);
359 }
360
GetSnapshots(JSContext * cx,nsCOMPtr<nsITelemetry> mTelemetry,const char * name,JS::MutableHandleValue valueOut,bool is_keyed)361 void GetSnapshots(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry,
362 const char* name, JS::MutableHandleValue valueOut,
363 bool is_keyed) {
364 JS::RootedValue snapshots(cx);
365 nsresult rv = is_keyed ? mTelemetry->GetSnapshotForKeyedHistograms(
366 NS_LITERAL_CSTRING("main"), false,
367 false /* filter */, cx, &snapshots)
368 : mTelemetry->GetSnapshotForHistograms(
369 NS_LITERAL_CSTRING("main"), false,
370 false /* filter */, cx, &snapshots);
371
372 JS::RootedValue snapshot(cx);
373 GetProperty(cx, "parent", snapshots, &snapshot);
374
375 ASSERT_EQ(rv, NS_OK) << "Cannot call histogram snapshots";
376 valueOut.set(snapshot);
377 }
378
379 } // namespace TelemetryTestHelpers
380