/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */"use strict";const{AppConstants}=ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs");const{setTimeout}=ChromeUtils.importESModule("resource://gre/modules/Timer.sys.mjs");functionsleep(ms){/* eslint-disable mozilla/no-arbitrary-setTimeout */returnnewPromise(resolve=>setTimeout(resolve,ms));}add_setup(/* on Android FOG is set up through head.js */{skip_if:()=>AppConstants.platform=="android"},functiontest_setup(){// FOG needs a profile directory to put its data in.do_get_profile();// We need to initialize it once, otherwise operations will be stuck in the pre-init queue.Services.fog.initializeFOG();});add_task(functiontest_fog_counter_works(){Glean.testOnly.badCode.add(31);Assert.equal(31,Glean.testOnly.badCode.testGetValue("test-ping"));});add_task(asyncfunctiontest_fog_string_works(){Assert.equal(null,Glean.testOnly.cheesyString.testGetValue());// Setting `undefined` will be ignored.Glean.testOnly.cheesyString.set(undefined);Assert.equal(null,Glean.testOnly.cheesyString.testGetValue());constvalue="a cheesy string!";Glean.testOnly.cheesyString.set(value);Assert.equal(value,Glean.testOnly.cheesyString.testGetValue("test-ping"));});add_task(asyncfunctiontest_fog_string_list_works(){constvalue="a cheesy string!";constvalue2="a cheesier string!";constvalue3="the cheeziest of strings.";constcheeseList=[value,value2];Glean.testOnly.cheesyStringList.set(cheeseList);letval=Glean.testOnly.cheesyStringList.testGetValue();// Note: This is incredibly fragile and will break if we ever rearrange items// in the string list.Assert.deepEqual(cheeseList,val);Glean.testOnly.cheesyStringList.add(value3);Assert.ok(Glean.testOnly.cheesyStringList.testGetValue().includes(value3));});add_task(asyncfunctiontest_fog_timespan_works(){Glean.testOnly.canWeTimeIt.start();Glean.testOnly.canWeTimeIt.cancel();Assert.equal(undefined,Glean.testOnly.canWeTimeIt.testGetValue());// We start, briefly sleep and then stop.// That guarantees some time to measure.Glean.testOnly.canWeTimeIt.start();awaitsleep(10);Glean.testOnly.canWeTimeIt.stop();Assert.greater(Glean.testOnly.canWeTimeIt.testGetValue("test-ping"),0);});add_task(asyncfunctiontest_fog_timespan_throws_on_stop_wout_start(){Glean.testOnly.canWeTimeIt.stop();Assert.throws(()=>Glean.testOnly.canWeTimeIt.testGetValue(),/DataError/,"Should throw because stop was called without start.");});add_task(asyncfunctiontest_fog_uuid_works(){constkTestUuid="decafdec-afde-cafd-ecaf-decafdecafde";Glean.testOnly.whatIdIt.set(kTestUuid);Assert.equal(kTestUuid,Glean.testOnly.whatIdIt.testGetValue("test-ping"));Glean.testOnly.whatIdIt.generateAndSet();// Since we generate v4 UUIDs, and the first character of the third group// isn't 4, this won't ever collide with kTestUuid.Assert.notEqual(kTestUuid,Glean.testOnly.whatIdIt.testGetValue("test-ping"));});add_task(functiontest_fog_datetime_works(){constvalue=newDate("2020-06-11T12:00:00");Glean.testOnly.whatADate.set(value.getTime()*1000);constreceived=Glean.testOnly.whatADate.testGetValue("test-ping");Assert.equal(received.getTime(),value.getTime());});add_task(functiontest_fog_boolean_works(){Glean.testOnly.canWeFlagIt.set(false);Assert.equal(false,Glean.testOnly.canWeFlagIt.testGetValue("test-ping"));// While you're here, might as well test that the ping name's optional.Assert.equal(false,Glean.testOnly.canWeFlagIt.testGetValue());});add_task(asyncfunctiontest_fog_event_works(){Glean.testOnlyIpc.noExtraEvent.record();varevents=Glean.testOnlyIpc.noExtraEvent.testGetValue();Assert.equal(1,events.length);Assert.equal("test_only.ipc",events[0].category);Assert.equal("no_extra_event",events[0].name);letextra={extra1:"can set extras",extra2:"passing more data"};Glean.testOnlyIpc.anEvent.record(extra);events=Glean.testOnlyIpc.anEvent.testGetValue();Assert.equal(1,events.length);Assert.equal("test_only.ipc",events[0].category);Assert.equal("an_event",events[0].name);Assert.deepEqual(extra,events[0].extra);// Corner case: Event with extra with `undefined` value.// Should pretend that extra key isn't there.extra={extra1:undefined,extra2:"defined"};Glean.testOnlyIpc.anEvent.record(extra);events=Glean.testOnlyIpc.anEvent.testGetValue();Assert.equal(2,events.length);Assert.deepEqual({extra2:"defined"},events[1].extra);letextra2={extra1:"can set extras",extra2:37,extra3_longer_name:false,};Glean.testOnlyIpc.eventWithExtra.record(extra2);events=Glean.testOnlyIpc.eventWithExtra.testGetValue();Assert.equal(1,events.length);Assert.equal("test_only.ipc",events[0].category);Assert.equal("event_with_extra",events[0].name);letexpectedExtra={extra1:"can set extras",extra2:"37",extra3_longer_name:"false",};Assert.deepEqual(expectedExtra,events[0].extra);// camelCase extras work.letextra5={extra4CamelCase:false,};Glean.testOnlyIpc.eventWithExtra.record(extra5);events=Glean.testOnlyIpc.eventWithExtra.testGetValue();Assert.equal(2,events.length,"Recorded one event too many.");expectedExtra={extra4CamelCase:"false",};Assert.deepEqual(expectedExtra,events[1].extra);// Passing `null` works.Glean.testOnlyIpc.eventWithExtra.record(null);events=Glean.testOnlyIpc.eventWithExtra.testGetValue();Assert.equal(3,events.length,"Recorded another event.");Assert.equal(events[2].extra,null);// Invalid extra keys don't crash, the event is not recorded,// but an error is recorded.letextra3={extra1_nonexistent_extra:"this does not crash",};Glean.testOnlyIpc.eventWithExtra.record(extra3);Assert.throws(()=>Glean.testOnlyIpc.eventWithExtra.testGetValue(),/DataError/,"Should throw because of a recording error.");// Supplying extras when there aren't any defined results in the event not// being recorded, but an error is.Glean.testOnlyIpc.noExtraEvent.record(extra3);Assert.throws(()=>Glean.testOnlyIpc.eventWithExtra.testGetValue(),/DataError/,"Should throw because of a recording error.");});add_task(asyncfunctiontest_fog_memory_distribution_works(){Glean.testOnly.doYouRemember.accumulate(7);Glean.testOnly.doYouRemember.accumulate(17);letdata=Glean.testOnly.doYouRemember.testGetValue("test-ping");Assert.equal(2,data.count,"Count of entries is correct");// `data.sum` is in bytes, but the metric is in MB.Assert.equal(24*1024*1024,data.sum,"Sum's correct");for(let[bucket,count]ofObject.entries(data.values)){Assert.ok(count==0||(count==1&&(bucket==17520006||bucket==7053950)),"Only two buckets have a sample");}});add_task(asyncfunctiontest_fog_custom_distribution_works(){Glean.testOnlyIpc.aCustomDist.accumulateSamples([7,268435458]);letdata=Glean.testOnlyIpc.aCustomDist.testGetValue("test-ping");Assert.equal(2,data.count,"Count of entries is correct");Assert.equal(7+268435458,data.sum,"Sum's correct");for(let[bucket,count]ofObject.entries(data.values)){Assert.ok(count==0||(count==1&&(bucket==1||bucket==268435456)),`Only two buckets have a sample ${bucket}${count}`);}// Negative values will not be recorded, instead an error is recorded.Glean.testOnlyIpc.aCustomDist.accumulateSamples([-7]);Assert.throws(()=>Glean.testOnlyIpc.aCustomDist.testGetValue(),/DataError/);});add_task(functiontest_fog_custom_pings(){Assert.ok("onePingOnly"inGleanPings);letsubmitted=false;Glean.testOnly.onePingOneBool.set(false);GleanPings.onePingOnly.testBeforeNextSubmit(()=>{submitted=true;Assert.equal(false,Glean.testOnly.onePingOneBool.testGetValue());});GleanPings.onePingOnly.submit();Assert.ok(submitted,"Ping was submitted, callback was called.");});add_task(functiontest_recursive_testBeforeNextSubmit(){Assert.ok("onePingOnly"inGleanPings);letsubmitted=0;letrec=()=>{submitted++;GleanPings.onePingOnly.testBeforeNextSubmit(rec);};GleanPings.onePingOnly.testBeforeNextSubmit(rec);GleanPings.onePingOnly.submit();GleanPings.onePingOnly.submit();GleanPings.onePingOnly.submit();Assert.equal(3,submitted,"Ping was submitted 3 times");// Be kind and remove the callback.GleanPings.onePingOnly.testBeforeNextSubmit(()=>{});});add_task(functiontest_testBeforeNextSubmit_error(){Assert.ok("onePingOnly"inGleanPings);letsubmitted=false;GleanPings.onePingOnly.testBeforeNextSubmit(()=>{submitted=true;thrownewError("oh no");});Assert.throws(()=>GleanPings.onePingOnly.submit(),/oh no/,"testBeforeNextSubmit error thrown from submit");Assert.ok(submitted,"Did submit ping");});add_task(asyncfunctiontest_testSubmission(){Assert.ok("onePingOnly"inGleanPings);letsubmitReason=null;awaitGleanPings.onePingOnly.testSubmission(reason=>(submitReason=reason),()=>GleanPings.onePingOnly.submit("raison d'être"));Assert.equal(submitReason,"raison d'être","ping callback called with correct reason");});add_task(asyncfunctiontest_testSubmission_async(){Assert.ok("onePingOnly"inGleanPings);constorderOfOperations=[];// We are going to intentionally block submission so that we can delay it// until after testSubmission returns a promise.constblocker=Promise.withResolvers();consttestPromise=GleanPings.onePingOnly.testSubmission(()=>orderOfOperations.push("test-callback"),async()=>{orderOfOperations.push("await-blocker");awaitblocker.promise;orderOfOperations.push("submit");GleanPings.onePingOnly.submit();});orderOfOperations.push("test-submission-queued");blocker.resolve();awaittestPromise;Assert.deepEqual(orderOfOperations,["await-blocker","test-submission-queued","submit","test-callback",]);});add_task(asyncfunctiontest_testSubmission_error(){Assert.ok("onePingOnly"inGleanPings);awaitAssert.rejects(GleanPings.onePingOnly.testSubmission(()=>{thrownewError("uh oh");},()=>GleanPings.onePingOnly.submit()),/NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS/,"testSubmission callback threw");});add_task(asyncfunctiontest_testSubmission_unsubmitted(){Assert.ok("onePingOnly"inGleanPings);letsubmitted=false;awaitAssert.rejects(GleanPings.onePingOnly.testSubmission(()=>(submitted=true),()=>{}),/Ping did not submit immediately/,"Threw immediately because the ping did not submit");Assert.ok(!submitted,"callback not called");});add_task(asyncfunctiontest_testSubmission_timeout(){Assert.ok("onePingOnly"inGleanPings);letsubmitted=false;awaitAssert.rejects(GleanPings.onePingOnly.testSubmission(()=>(submitted=true),()=>{},1),/Ping was not submitted after timeout/,"Threw after a timeout");Assert.ok(!submitted,"callback not called");});add_task(asyncfunctiontest_fog_timing_distribution_works(){lett1=Glean.testOnly.whatTimeIsIt.start();lett2=Glean.testOnly.whatTimeIsIt.start();awaitsleep(5);lett3=Glean.testOnly.whatTimeIsIt.start();Glean.testOnly.whatTimeIsIt.cancel(t1);awaitsleep(5);Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t2);// 10msGlean.testOnly.whatTimeIsIt.stopAndAccumulate(t3);// 5ms// samples are measured in microseconds, since that's the unit listed in metrics.yamlGlean.testOnly.whatTimeIsIt.accumulateSingleSample(5000);// 5msGlean.testOnly.whatTimeIsIt.accumulateSamples([2000,8000]);// 10msletdata=Glean.testOnly.whatTimeIsIt.testGetValue();// Cancelled timers should not be counted.Assert.equal(5,data.count,"Count of entries is correct");constNANOS_IN_MILLIS=1e6;// bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough.constEPSILON=40000;// Variance in timing makes getting the sum impossible to know.Assert.greater(data.sum,30*NANOS_IN_MILLIS-EPSILON);// No guarantees from timers means no guarantees on buckets.// But we can guarantee it's only five samples.Assert.equal(5,Object.entries(data.values).reduce((acc,[,count])=>acc+count,0),"Only five buckets with samples");});add_task(asyncfunctiontest_fog_labels_conform(){Glean.testOnly.mabelsLabelMaker.singleword.set("portmanteau");Assert.equal("portmanteau",Glean.testOnly.mabelsLabelMaker.singleword.testGetValue());Glean.testOnly.mabelsLabelMaker.snake_case.set("snek");Assert.equal("snek",Glean.testOnly.mabelsLabelMaker.snake_case.testGetValue());Glean.testOnly.mabelsLabelMaker["dash-character"].set("Dash Rendar");Assert.equal("Dash Rendar",Glean.testOnly.mabelsLabelMaker["dash-character"].testGetValue());Glean.testOnly.mabelsLabelMaker["dot.separated"].set("dot product");Assert.equal("dot product",Glean.testOnly.mabelsLabelMaker["dot.separated"].testGetValue());Glean.testOnly.mabelsLabelMaker.camelCase.set("wednesday");Assert.equal("wednesday",Glean.testOnly.mabelsLabelMaker.camelCase.testGetValue());constveryLong="1".repeat(112);Glean.testOnly.mabelsLabelMaker[veryLong].set("seventy-two");Assert.throws(()=>Glean.testOnly.mabelsLabelMaker[veryLong].testGetValue(),/DataError/,"Should throw because of an invalid label.");// This test should _now_ throw because we are calling data after an invalid// label has been set.Assert.throws(()=>Glean.testOnly.mabelsLabelMaker["dot.separated"].testGetValue(),/DataError/,"Should throw because of an invalid label.");});add_task(asyncfunctiontest_fog_labeled_boolean_works(){Assert.equal(undefined,Glean.testOnly.mabelsLikeBalloons.at_parties.testGetValue(),"New labels with no values should return undefined");Glean.testOnly.mabelsLikeBalloons.at_parties.set(true);Glean.testOnly.mabelsLikeBalloons.at_funerals.set(false);Assert.equal(true,Glean.testOnly.mabelsLikeBalloons.at_parties.testGetValue());Assert.equal(false,Glean.testOnly.mabelsLikeBalloons.at_funerals.testGetValue());// What about invalid/__other__?Assert.equal(undefined,Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue());Glean.testOnly.mabelsLikeBalloons["1".repeat(112)].set(true);Assert.throws(()=>Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue(),/DataError/,"Should throw because of a recording error.");});add_task(asyncfunctiontest_fog_labeled_counter_works(){Assert.equal(undefined,Glean.testOnly.mabelsKitchenCounters.near_the_sink.testGetValue(),"New labels with no values should return undefined");Glean.testOnly.mabelsKitchenCounters.near_the_sink.add(1);Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.add(2);Assert.equal(1,Glean.testOnly.mabelsKitchenCounters.near_the_sink.testGetValue());Assert.equal(2,Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.testGetValue());// What about invalid/__other__?Assert.equal(undefined,Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue());Glean.testOnly.mabelsKitchenCounters["1".repeat(112)].add(1);Assert.throws(()=>Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue(),/DataError/,"Should throw because of a recording error.");});add_task(asyncfunctiontest_fog_labeled_string_works(){Assert.equal(undefined,Glean.testOnly.mabelsBalloonStrings.colour_of_99.testGetValue(),"New labels with no values should return undefined");Glean.testOnly.mabelsBalloonStrings.colour_of_99.set("crimson");Glean.testOnly.mabelsBalloonStrings.string_lengths.set("various");Assert.equal("crimson",Glean.testOnly.mabelsBalloonStrings.colour_of_99.testGetValue());Assert.equal("various",Glean.testOnly.mabelsBalloonStrings.string_lengths.testGetValue());// What about invalid/__other__?Assert.equal(undefined,Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue());Glean.testOnly.mabelsBalloonStrings["1".repeat(112)].set("valid");Assert.throws(()=>Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue(),/DataError/);});add_task(functiontest_fog_quantity_works(){Glean.testOnly.meaningOfLife.set(42);Assert.equal(42,Glean.testOnly.meaningOfLife.testGetValue());});add_task(functiontest_fog_rate_works(){// 1) Standard rate with internal denominatorGlean.testOnlyIpc.irate.addToNumerator(22);Glean.testOnlyIpc.irate.addToDenominator(7);Assert.deepEqual({numerator:22,denominator:7},Glean.testOnlyIpc.irate.testGetValue());// 2) Rate with external denominatorGlean.testOnlyIpc.anExternalDenominator.add(11);Glean.testOnlyIpc.rateWithExternalDenominator.addToNumerator(121);Assert.equal(11,Glean.testOnlyIpc.anExternalDenominator.testGetValue());Assert.deepEqual({numerator:121,denominator:11},Glean.testOnlyIpc.rateWithExternalDenominator.testGetValue());});add_task(asyncfunctiontest_fog_url_works(){constvalue="https://www.example.com/fog";Glean.testOnlyIpc.aUrl.set(value);Assert.equal(value,Glean.testOnlyIpc.aUrl.testGetValue("test-ping"));});add_task(asyncfunctiontest_fog_text_works(){constvalue="Before the risin' sun, we fly, So many roads to choose, We'll start out walkin' and learn to run, (We've only just begun)";Glean.testOnlyIpc.aText.set(value);letrslt=Glean.testOnlyIpc.aText.testGetValue();Assert.equal(value,rslt);Assert.equal(121,rslt.length);});add_task(asyncfunctiontest_fog_text_works_unusual_character(){constvalue="The secret to Dominique Ansel's viennoiserie is the use of Isigny Sainte-Mère butter and Les Grands Moulins de Paris flour";Glean.testOnlyIpc.aText.set(value);letrslt=Glean.testOnlyIpc.aText.testGetValue();Assert.equal(value,rslt);Assert.greater(rslt.length,100);});add_task(asyncfunctiontest_fog_object_works(){Assert.equal(undefined,Glean.testOnly.balloons.testGetValue(),"No object stored");// Can't store not-objects.letinvalidValues=[1,"str",false,undefined,null,NaN,Infinity];for(letvalueofinvalidValues){Assert.throws(()=>Glean.testOnly.balloons.set(value),/is not an object/,"Should throw a type error");}// No invalid value will be stored.Assert.equal(undefined,Glean.testOnly.balloons.testGetValue(),"No object stored");// `JS_Stringify` internally throws// an `TypeError: cyclic object value` exception.// That's cleared and `set` should not throw on it.// This eventually should log a proper error in Glean.letselfref={};selfref.a=selfref;Glean.testOnly.balloons.set(selfref);Assert.equal(undefined,Glean.testOnly.balloons.testGetValue(),"No object stored");letballoons=[{colour:"red",diameter:5},{colour:"blue",diameter:7},{colour:"orange"},];Glean.testOnly.balloons.set(balloons);letresult=Glean.testOnly.balloons.testGetValue();letexpected=[{colour:"red",diameter:5},{colour:"blue",diameter:7},{colour:"orange"},];Assert.deepEqual(expected,result);});add_task(// FIXME(bug 1947194): JOG object metrics don't do schema validation yet{skip_if:()=>Services.prefs.getBoolPref("telemetry.fog.artifact_build",false),},asyncfunctiontest_fog_object_verifies_structure(){// These values are coerced to null or removed.letballoons=[{colour:"inf",diameter:Infinity},{colour:"negative-inf",diameter:-1/0},{colour:"nan",diameter:NaN},{colour:"undef",diameter:undefined},];Glean.testOnly.balloons.set(balloons);letresult=Glean.testOnly.balloons.testGetValue();letexpected=[{colour:"inf"},{colour:"negative-inf"},{colour:"nan"},{colour:"undef"},];Assert.deepEqual(expected,result);// colour != color.letinvalid=[{color:"orange"},{color:"red",diameter:"small"}];Glean.testOnly.balloons.set(invalid);Assert.throws(()=>Glean.testOnly.balloons.testGetValue(),/invalid_value/,"Should throw because last object was invalid.");Services.fog.testResetFOG();// set again to ensure it's storedballoons=[{colour:"red",diameter:5},{colour:"blue",diameter:7},];Glean.testOnly.balloons.set(balloons);result=Glean.testOnly.balloons.testGetValue();Assert.deepEqual(balloons,result);invalid=[{colour:"red",diameter:5,extra:"field"}];Glean.testOnly.balloons.set(invalid);Assert.throws(()=>Glean.testOnly.balloons.testGetValue(),/invalid_value/,"Should throw because last object was invalid.");});add_task(asyncfunctiontest_fog_complex_object_works(){if(!Glean.testOnly.crashStack){// FIXME(bug 1883857): object metric type not available, e.g. in artifact builds.// Skipping this test.return;}Assert.equal(undefined,Glean.testOnly.crashStack.testGetValue(),"No object stored");Glean.testOnly.crashStack.set({});letresult=Glean.testOnly.crashStack.testGetValue();Assert.deepEqual({},result);letstack={status:"OK",crash_info:{typ:"main",address:"0xf001ba11",crashing_thread:1,},main_module:0,modules:[{base_addr:"0x00000000",end_addr:"0x00004000",},],};Glean.testOnly.crashStack.set(stack);result=Glean.testOnly.crashStack.testGetValue();Assert.deepEqual(stack,result);stack={status:"OK",modules:[{base_addr:"0x00000000",end_addr:"0x00004000",},],};Glean.testOnly.crashStack.set(stack);result=Glean.testOnly.crashStack.testGetValue();Assert.deepEqual(stack,result);// FIXME(bug 1947194): JOG object metrics don't do schema validation yetif(!Services.prefs.getBoolPref("telemetry.fog.artifact_build",false)){stack={status:"OK",modules:[],};Glean.testOnly.crashStack.set(stack);result=Glean.testOnly.crashStack.testGetValue();Assert.deepEqual({status:"OK"},result);stack={status:"OK",};Glean.testOnly.crashStack.set(stack);result=Glean.testOnly.crashStack.testGetValue();Assert.deepEqual(stack,result);}});add_task(// FIXME(1898464): ride-along pings are not handled correctly in artifact builds.{skip_if:()=>Services.prefs.getBoolPref("telemetry.fog.artifact_build",false),},functiontest_fog_ride_along_pings(){Assert.equal(null,Glean.testOnly.badCode.testGetValue("test-ping"));Assert.equal(null,Glean.testOnly.badCode.testGetValue("ride-along-ping"));Glean.testOnly.badCode.add(37);Assert.equal(37,Glean.testOnly.badCode.testGetValue("test-ping"));Assert.equal(37,Glean.testOnly.badCode.testGetValue("ride-along-ping"));lettestPingSubmitted=false;GleanPings.testPing.testBeforeNextSubmit(()=>{testPingSubmitted=true;});// FIXME(bug 1896356):// We can't use `testBeforeNextSubmit` for `ride-along-ping`// because it's triggered internally, but the callback would only be available// in the C++ bits, not in the internal Rust parts.// Submit only a single ping, the other will ride along.GleanPings.testPing.submit();Assert.ok(testPingSubmitted,"Test ping was submitted, callback was called.");// Both pings have been submitted, so the values should be cleared.Assert.equal(null,Glean.testOnly.badCode.testGetValue("test-ping"));Assert.equal(null,Glean.testOnly.badCode.testGetValue("ride-along-ping"));});add_task(asyncfunctiontest_fog_labeled_custom_distribution_works(){Assert.equal(undefined,Glean.testOnly.mabelsCustomLabelLengths.monospace.testGetValue(),"New labels with no values should return undefined");Glean.testOnly.mabelsCustomLabelLengths.monospace.accumulateSamples([1,42]);Glean.testOnly.mabelsCustomLabelLengths.sanserif.accumulateSingleSample(13);letmonospace=Glean.testOnly.mabelsCustomLabelLengths.monospace.testGetValue();Assert.equal(2,monospace.count);Assert.equal(43,monospace.sum);Assert.deepEqual({0:0,1:2,268435456:0},monospace.values);letsanserif=Glean.testOnly.mabelsCustomLabelLengths.sanserif.testGetValue();Assert.equal(1,sanserif.count);Assert.equal(13,sanserif.sum);Assert.deepEqual({0:0,1:1,268435456:0},sanserif.values);// What about invalid/__other__?Assert.equal(undefined,Glean.testOnly.mabelsCustomLabelLengths.__other__.testGetValue());Glean.testOnly.mabelsCustomLabelLengths["1".repeat(112)].accumulateSingleSample(3);Assert.throws(()=>Glean.testOnly.mabelsCustomLabelLengths.__other__.testGetValue(),/DataError/);});add_task(asyncfunctiontest_fog_labeled_memory_distribution_works(){Glean.testOnly.whatDoYouRemember.twenty_years_ago.accumulate(7);Glean.testOnly.whatDoYouRemember.twenty_years_ago.accumulate(17);letdata=Glean.testOnly.whatDoYouRemember.twenty_years_ago.testGetValue();Assert.equal(2,data.count,"Count of entries is correct");// `data.sum` is in bytes, but the metric is in MB.Assert.equal(24*1024*1024,data.sum,"Sum's correct");for(let[bucket,count]ofObject.entries(data.values)){Assert.ok(count==0||(count==1&&(bucket==17520006||bucket==7053950)),"Only two buckets have a sample");}});add_task(asyncfunctiontest_labeled_timing_distribution_works(){lett1=Glean.testOnly.whereHasTheTimeGone.west.start();lett2=Glean.testOnly.whereHasTheTimeGone.west.start();awaitsleep(5);lett3=Glean.testOnly.whereHasTheTimeGone.west.start();Glean.testOnly.whereHasTheTimeGone.west.cancel(t1);awaitsleep(5);Glean.testOnly.whereHasTheTimeGone.west.stopAndAccumulate(t2);// 10msGlean.testOnly.whereHasTheTimeGone.west.stopAndAccumulate(t3);// 5msletdata=Glean.testOnly.whereHasTheTimeGone.west.testGetValue();// Cancelled timers should not be counted.Assert.equal(2,data.count,"Count of entries is correct");constNANOS_IN_MILLIS=1e6;// bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough.constEPSILON=40000;// Variance in timing makes getting the sum impossible to know.Assert.greater(data.sum,15*NANOS_IN_MILLIS-EPSILON);// No guarantees from timers means no guarantees on buckets.// But we can guarantee it's only two samples.Assert.equal(2,Object.entries(data.values).reduce((acc,[,count])=>acc+count,0),"Only two buckets with samples");});add_task(asyncfunctiontest_fog_labeled_quantity_works(){Assert.equal(undefined,Glean.testOnly.buttonJars.up.testGetValue(),"New labels with no values should return undefined");Glean.testOnly.buttonJars.up.set(2);Glean.testOnly.buttonJars.curling.set(0);Assert.equal(2,Glean.testOnly.buttonJars.up.testGetValue());Assert.equal(0,Glean.testOnly.buttonJars.curling.testGetValue());// What about invalid/__other__?Assert.equal(undefined,Glean.testOnly.buttonJars.__other__.testGetValue());Glean.testOnly.buttonJars["1".repeat(112)].set(0);Assert.throws(()=>Glean.testOnly.buttonJars.__other__.testGetValue(),/DataError/,"Should throw because of a recording error.");});add_task(asyncfunctiontest_submit_throws(){GleanPings.onePingOnly.testBeforeNextSubmit(()=>{thrownewError("inside callback");});Assert.throws(()=>GleanPings.onePingOnly.submit(),/inside callback/,"Should throw inside callback");});add_task(functiontest_collection_disabled_pings_work(){// This test should work equally for full builds and artifact builds.Assert.ok("collectionDisabledPing"inGleanPings);// collection-enabled=false pings are disabled by default.// No data is collected for metrics going into that ping.Glean.testOnly.collectionDisabledCounter.add(1);Assert.equal(undefined,Glean.testOnly.collectionDisabledCounter.testGetValue());// After enabling a ping we can record data into itGleanPings.collectionDisabledPing.setEnabled(true);Glean.testOnly.collectionDisabledCounter.add(2);Assert.equal(2,Glean.testOnly.collectionDisabledCounter.testGetValue());letsubmitted=false;GleanPings.collectionDisabledPing.testBeforeNextSubmit(()=>{submitted=true;Assert.equal(2,Glean.testOnly.collectionDisabledCounter.testGetValue());});GleanPings.collectionDisabledPing.submit();Assert.ok(submitted,"Ping was submitted, callback was called.");Assert.equal(undefined,Glean.testOnly.collectionDisabledCounter.testGetValue());});add_task(functiontest_dual_labeled_counter_works(){Glean.testOnly.keyedCategories.get("to the city","lasered").add(1);Glean.testOnly.keyedCategories.get("to the city","cut").add(4);Glean.testOnly.keyedCategories.get("to my heart","polished").add(1);Assert.equal(1,Glean.testOnly.keyedCategories.get("to the city","lasered").testGetValue());Assert.equal(4,Glean.testOnly.keyedCategories.get("to the city","cut").testGetValue());Assert.equal(1,Glean.testOnly.keyedCategories.get("to my heart","polished").testGetValue());Assert.equal(undefined,Glean.testOnly.keyedCategories.get("to the city","__other__").testGetValue());Glean.testOnly.keyedCategories.get("to the city","cryptographic").add(3);Assert.equal(3,Glean.testOnly.keyedCategories.get("to the city","__other__").testGetValue());});