diff options
Diffstat (limited to 'toolkit/components/glean/tests/gtest')
-rw-r--r-- | toolkit/components/glean/tests/gtest/Cargo.toml | 14 | ||||
-rw-r--r-- | toolkit/components/glean/tests/gtest/FOGFixture.h | 23 | ||||
-rw-r--r-- | toolkit/components/glean/tests/gtest/TestFog.cpp | 470 | ||||
-rw-r--r-- | toolkit/components/glean/tests/gtest/moz.build | 14 | ||||
-rw-r--r-- | toolkit/components/glean/tests/gtest/test.rs | 55 |
5 files changed, 576 insertions, 0 deletions
diff --git a/toolkit/components/glean/tests/gtest/Cargo.toml b/toolkit/components/glean/tests/gtest/Cargo.toml new file mode 100644 index 0000000000..a744517e56 --- /dev/null +++ b/toolkit/components/glean/tests/gtest/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "fog-gtest" +version = "0.1.0" +authors = ["glean-team@mozilla.com"] +license = "MPL-2.0" +description = "Tests for the FOG crate" + +[dependencies] +firefox-on-glean = { path = "../../api" } +jog = { path = "../../bindings/jog" } +nsstring = { path = "../../../../../xpcom/rust/nsstring" } + +[lib] +path = "test.rs" diff --git a/toolkit/components/glean/tests/gtest/FOGFixture.h b/toolkit/components/glean/tests/gtest/FOGFixture.h new file mode 100644 index 0000000000..da3c6fc123 --- /dev/null +++ b/toolkit/components/glean/tests/gtest/FOGFixture.h @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#ifndef FOGFixture_h_ +#define FOGFixture_h_ + +#include "gtest/gtest.h" +#include "mozilla/glean/fog_ffi_generated.h" +#include "nsString.h" + +using namespace mozilla::glean::impl; + +class FOGFixture : public ::testing::Test { + protected: + FOGFixture() = default; + virtual void SetUp() { + nsCString empty; + ASSERT_EQ(NS_OK, fog_test_reset(&empty, &empty)); + } +}; + +#endif // FOGFixture_h_ diff --git a/toolkit/components/glean/tests/gtest/TestFog.cpp b/toolkit/components/glean/tests/gtest/TestFog.cpp new file mode 100644 index 0000000000..2de8f9ba4d --- /dev/null +++ b/toolkit/components/glean/tests/gtest/TestFog.cpp @@ -0,0 +1,470 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FOGFixture.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/glean/GleanPings.h" +#include "mozilla/glean/fog_ffi_generated.h" +#include "mozilla/Maybe.h" +#include "mozilla/Result.h" +#include "mozilla/ResultVariant.h" + +#include "nsTArray.h" + +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "nsString.h" +#include "prtime.h" + +using mozilla::Preferences; +using namespace mozilla::glean; +using namespace mozilla::glean::impl; + +#define DATA_PREF "datareporting.healthreport.uploadEnabled" + +extern "C" { +// This function is called by the rust code in test.rs if a non-fatal test +// failure occurs. +void GTest_FOG_ExpectFailure(const char* aMessage) { + EXPECT_STREQ(aMessage, ""); +} +} + +TEST_F(FOGFixture, BuiltinPingsRegistered) { + Preferences::SetInt("telemetry.fog.test.localhost_port", -1); + nsAutoCString metricsPingName("metrics"); + nsAutoCString baselinePingName("baseline"); + nsAutoCString eventsPingName("events"); + ASSERT_EQ(NS_OK, fog_submit_ping(&metricsPingName)); + ASSERT_EQ(NS_OK, fog_submit_ping(&baselinePingName)); + ASSERT_EQ(NS_OK, fog_submit_ping(&eventsPingName)); +} + +TEST_F(FOGFixture, TestCppCounterWorks) { + mozilla::glean::test_only::bad_code.Add(42); + + ASSERT_EQ(42, mozilla::glean::test_only::bad_code.TestGetValue("test-ping"_ns) + .unwrap() + .value()); + // And test that the ping name's optional, while you're at it: + ASSERT_EQ(42, test_only::bad_code.TestGetValue().unwrap().value()); +} + +TEST_F(FOGFixture, TestCppStringWorks) { + auto kValue = "cheez!"_ns; + mozilla::glean::test_only::cheesy_string.Set(kValue); + + ASSERT_STREQ(kValue.get(), mozilla::glean::test_only::cheesy_string + .TestGetValue("test-ping"_ns) + .unwrap() + .value() + .get()); +} + +TEST_F(FOGFixture, TestCppTimespanWorks) { + mozilla::glean::test_only::can_we_time_it.Start(); + PR_Sleep(PR_MillisecondsToInterval(10)); + mozilla::glean::test_only::can_we_time_it.Stop(); + + ASSERT_TRUE( + mozilla::glean::test_only::can_we_time_it.TestGetValue("test-ping"_ns) + .unwrap() + .value() > 0); +} + +TEST_F(FOGFixture, TestCppUuidWorks) { + nsCString kTestUuid("decafdec-afde-cafd-ecaf-decafdecafde"); + test_only::what_id_it.Set(kTestUuid); + ASSERT_STREQ(kTestUuid.get(), + test_only::what_id_it.TestGetValue("test-ping"_ns) + .unwrap() + .value() + .get()); + + test_only::what_id_it.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_STRNE(kTestUuid.get(), + test_only::what_id_it.TestGetValue("test-ping"_ns) + .unwrap() + .value() + .get()); +} + +TEST_F(FOGFixture, TestCppBooleanWorks) { + mozilla::glean::test_only::can_we_flag_it.Set(false); + + ASSERT_EQ(false, mozilla::glean::test_only::can_we_flag_it + .TestGetValue("test-ping"_ns) + .unwrap() + .value()); +} + +MATCHER_P(BitEq, x, "bit equal") { + static_assert(sizeof(x) == sizeof(arg)); + return std::memcmp(&arg, &x, sizeof(x)) == 0; +} + +TEST_F(FOGFixture, TestCppDatetimeWorks) { + PRExplodedTime date{0, 35, 10, 12, 6, 10, 2020, 0, 0, {5 * 60 * 60, 0}}; + test_only::what_a_date.Set(&date); + + auto received = test_only::what_a_date.TestGetValue("test-ping"_ns).unwrap(); + ASSERT_THAT(received.value(), BitEq(date)); +} + +using mozilla::Some; +using mozilla::glean::test_only_ipc::AnEventExtra; +using mozilla::glean::test_only_ipc::EventWithExtraExtra; +using std::tuple; + +TEST_F(FOGFixture, TestCppEventWorks) { + test_only_ipc::no_extra_event.Record(); + ASSERT_TRUE(test_only_ipc::no_extra_event.TestGetValue("store1"_ns) + .unwrap() + .isSome()); + + AnEventExtra extra = {.extra1 = Some("can set extras"_ns)}; + test_only_ipc::an_event.Record(Some(extra)); + auto optEvents = test_only_ipc::an_event.TestGetValue("store1"_ns).unwrap(); + ASSERT_TRUE(optEvents.isSome()); + + auto events = optEvents.extract(); + ASSERT_EQ(1UL, events.Length()); + ASSERT_STREQ("test_only.ipc", events[0].mCategory.get()); + ASSERT_STREQ("an_event", events[0].mName.get()); + ASSERT_EQ(1UL, events[0].mExtra.Length()); + ASSERT_STREQ("extra1", std::get<0>(events[0].mExtra[0]).get()); + ASSERT_STREQ("can set extras", std::get<1>(events[0].mExtra[0]).get()); +} + +TEST_F(FOGFixture, TestCppEventsWithDifferentExtraTypes) { + EventWithExtraExtra extra = {.extra1 = Some("can set extras"_ns), + .extra2 = Some(37), + .extra3LongerName = Some(false)}; + test_only_ipc::event_with_extra.Record(Some(extra)); + auto optEvents = + test_only_ipc::event_with_extra.TestGetValue("store1"_ns).unwrap(); + ASSERT_TRUE(optEvents.isSome()); + + auto events = optEvents.extract(); + ASSERT_EQ(1UL, events.Length()); + + // The list of extra key/value pairs can be in any order. + ASSERT_EQ(3UL, events[0].mExtra.Length()); + for (auto extra : events[0].mExtra) { + auto key = std::get<0>(extra); + auto value = std::get<1>(extra); + + if (key == "extra1"_ns) { + ASSERT_STREQ("can set extras", value.get()); + } else if (key == "extra2"_ns) { + ASSERT_STREQ("37", value.get()); + } else if (key == "extra3_longer_name"_ns) { + ASSERT_STREQ("false", value.get()); + } else { + ASSERT_TRUE(false) + << "Invalid extra item recorded."; + } + } +} + +TEST_F(FOGFixture, TestCppMemoryDistWorks) { + test_only::do_you_remember.Accumulate(7); + test_only::do_you_remember.Accumulate(17); + + DistributionData data = + test_only::do_you_remember.TestGetValue("test-ping"_ns).unwrap().ref(); + // Sum is in bytes, test_only::do_you_remember is in megabytes. So + // multiplication ahoy! + ASSERT_EQ(data.sum, 24UL * 1024 * 1024); + for (const auto& entry : data.values) { + const uint64_t bucket = entry.GetKey(); + const uint64_t count = entry.GetData(); + ASSERT_TRUE(count == 0 || + (count == 1 && (bucket == 17520006 || bucket == 7053950))) + << "Only two occupied buckets"; + } +} + +TEST_F(FOGFixture, TestCppCustomDistWorks) { + test_only_ipc::a_custom_dist.AccumulateSamples({7, 268435458}); + + DistributionData data = + test_only_ipc::a_custom_dist.TestGetValue("store1"_ns).unwrap().ref(); + ASSERT_EQ(data.sum, 7UL + 268435458); + for (const auto& entry : data.values) { + const uint64_t bucket = entry.GetKey(); + const uint64_t count = entry.GetData(); + ASSERT_TRUE(count == 0 || + (count == 1 && (bucket == 1 || bucket == 268435456))) + << "Only two occupied buckets"; + } +} + +TEST_F(FOGFixture, TestCppPings) { + test_only::one_ping_one_bool.Set(false); + const auto& ping = mozilla::glean_pings::OnePingOnly; + bool submitted = false; + ping.TestBeforeNextSubmit([&submitted](const nsACString& aReason) { + submitted = true; + ASSERT_EQ(false, + test_only::one_ping_one_bool.TestGetValue().unwrap().ref()); + }); + ping.Submit(); + ASSERT_TRUE(submitted) + << "Must have actually called the lambda."; +} + +TEST_F(FOGFixture, TestCppStringLists) { + auto kValue = "cheez!"_ns; + auto kValue2 = "cheezier!"_ns; + auto kValue3 = "cheeziest."_ns; + + nsTArray<nsCString> cheezList; + cheezList.EmplaceBack(kValue); + cheezList.EmplaceBack(kValue2); + + test_only::cheesy_string_list.Set(cheezList); + + auto val = test_only::cheesy_string_list.TestGetValue().unwrap().value(); + // Note: This is fragile if the order is ever not preserved. + ASSERT_STREQ(kValue.get(), val[0].get()); + ASSERT_STREQ(kValue2.get(), val[1].get()); + + test_only::cheesy_string_list.Add(kValue3); + + val = test_only::cheesy_string_list.TestGetValue().unwrap().value(); + ASSERT_STREQ(kValue3.get(), val[2].get()); +} + +TEST_F(FOGFixture, TestCppTimingDistWorks) { + auto id1 = test_only::what_time_is_it.Start(); + auto id2 = test_only::what_time_is_it.Start(); + PR_Sleep(PR_MillisecondsToInterval(5)); + auto id3 = test_only::what_time_is_it.Start(); + test_only::what_time_is_it.Cancel(std::move(id1)); + PR_Sleep(PR_MillisecondsToInterval(5)); + test_only::what_time_is_it.StopAndAccumulate(std::move(id2)); + test_only::what_time_is_it.StopAndAccumulate(std::move(id3)); + + DistributionData data = + test_only::what_time_is_it.TestGetValue().unwrap().ref(); + const uint64_t NANOS_IN_MILLIS = 1e6; + + // bug 1701847 - Sleeps don't necessarily round up as you'd expect. + // Give ourselves a 200000ns (0.2ms) window to be off on fast machines. + const uint64_t EPSILON = 200000; + + // We don't know exactly how long those sleeps took, only that it was at + // least 15ms total. + ASSERT_GT(data.sum, (uint64_t)(15 * NANOS_IN_MILLIS) - EPSILON); + + // We also can't guarantee the buckets, but we can guarantee two samples. + uint64_t sampleCount = 0; + for (const auto& value : data.values.Values()) { + sampleCount += value; + } + ASSERT_EQ(sampleCount, (uint64_t)2); +} + +TEST_F(FOGFixture, TestLabeledBooleanWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_like_balloons.Get("hot_air"_ns) + .TestGetValue() + .unwrap()); + test_only::mabels_like_balloons.Get("hot_air"_ns).Set(true); + test_only::mabels_like_balloons.Get("helium"_ns).Set(false); + ASSERT_EQ(true, test_only::mabels_like_balloons.Get("hot_air"_ns) + .TestGetValue() + .unwrap() + .ref()); + ASSERT_EQ(false, test_only::mabels_like_balloons.Get("helium"_ns) + .TestGetValue() + .unwrap() + .ref()); +} + +TEST_F(FOGFixture, TestLabeledBooleanWithLabelsWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eWater) + .TestGetValue() + .unwrap()); + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eWater) + .Set(true); + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eBirthdayParty) + .Set(false); + ASSERT_EQ(true, + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eWater) + .TestGetValue() + .unwrap() + .ref()); + ASSERT_EQ( + false, + test_only::mabels_like_labeled_balloons + .EnumGet(test_only::MabelsLikeLabeledBalloonsLabel::eBirthdayParty) + .TestGetValue() + .unwrap() + .ref()); +} + +TEST_F(FOGFixture, TestLabeledCounterWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_kitchen_counters.Get("marble"_ns) + .TestGetValue() + .unwrap()); + test_only::mabels_kitchen_counters.Get("marble"_ns).Add(1); + test_only::mabels_kitchen_counters.Get("laminate"_ns).Add(2); + ASSERT_EQ(1, test_only::mabels_kitchen_counters.Get("marble"_ns) + .TestGetValue() + .unwrap() + .ref()); + ASSERT_EQ(2, test_only::mabels_kitchen_counters.Get("laminate"_ns) + .TestGetValue() + .unwrap() + .ref()); +} + +TEST_F(FOGFixture, TestLabeledCounterWithLabelsWorks) { + ASSERT_EQ( + mozilla::Nothing(), + test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eNextToTheFridge) + .TestGetValue() + .unwrap()); + test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eNextToTheFridge) + .Add(1); + test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eClean) + .Add(2); + ASSERT_EQ( + 1, test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eNextToTheFridge) + .TestGetValue() + .unwrap() + .ref()); + ASSERT_EQ(2, test_only::mabels_labeled_counters + .EnumGet(test_only::MabelsLabeledCountersLabel::eClean) + .TestGetValue() + .unwrap() + .ref()); +} + +TEST_F(FOGFixture, TestLabeledStringWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_balloon_strings.Get("twine"_ns) + .TestGetValue() + .unwrap()); + test_only::mabels_balloon_strings.Get("twine"_ns).Set("seems acceptable"_ns); + test_only::mabels_balloon_strings.Get("parachute_cord"_ns) + .Set("preferred"_ns); + ASSERT_STREQ("seems acceptable", + test_only::mabels_balloon_strings.Get("twine"_ns) + .TestGetValue() + .unwrap() + .ref() + .get()); + ASSERT_STREQ("preferred", + test_only::mabels_balloon_strings.Get("parachute_cord"_ns) + .TestGetValue() + .unwrap() + .ref() + .get()); +} + +TEST_F(FOGFixture, TestLabeledStringWithLabelsWorks) { + ASSERT_EQ(mozilla::Nothing(), + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratory) + .TestGetValue() + .unwrap()); + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratory) + .Set("for birthdays, etc."_ns); + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratoryAndSnarky) + .Set("for retirements and bridal showers"_ns); + ASSERT_STREQ("for birthdays, etc.", + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratory) + .TestGetValue() + .unwrap() + .ref() + .get()); + ASSERT_STREQ( + "for retirements and bridal showers", + test_only::mabels_balloon_labels + .EnumGet(test_only::MabelsBalloonLabelsLabel::eCelebratoryAndSnarky) + .TestGetValue() + .unwrap() + .ref() + .get()); +} + +TEST_F(FOGFixture, TestCppQuantityWorks) { + // This joke only works in base 13. + const uint32_t kValue = 6 * 9; + mozilla::glean::test_only::meaning_of_life.Set(kValue); + + ASSERT_EQ(kValue, mozilla::glean::test_only::meaning_of_life.TestGetValue() + .unwrap() + .value()); +} + +TEST_F(FOGFixture, TestCppRateWorks) { + // 1) Standard rate with internal denominator + const int32_t kNum = 22; + const int32_t kDen = 7; // because I like pi, even just approximately. + + test_only_ipc::irate.AddToNumerator(kNum); + test_only_ipc::irate.AddToDenominator(kDen); + auto value = test_only_ipc::irate.TestGetValue().unwrap(); + ASSERT_EQ(kNum, value.ref().first); + ASSERT_EQ(kDen, value.ref().second); + + // 2) Rate with external denominator + test_only_ipc::rate_with_external_denominator.AddToNumerator(kNum); + test_only_ipc::an_external_denominator.Add(kDen); + value = test_only_ipc::rate_with_external_denominator.TestGetValue().unwrap(); + ASSERT_EQ(kNum, value.ref().first); + ASSERT_EQ(kDen, value.ref().second); + ASSERT_EQ( + kDen, + test_only_ipc::an_external_denominator.TestGetValue().unwrap().extract()); +} + +TEST_F(FOGFixture, TestCppUrlWorks) { + auto kValue = "https://example.com/fog/gtest"_ns; + mozilla::glean::test_only_ipc::a_url.Set(kValue); + + ASSERT_STREQ(kValue.get(), + mozilla::glean::test_only_ipc::a_url.TestGetValue("store1"_ns) + .unwrap() + .value() + .get()); +} + +TEST_F(FOGFixture, TestCppTextWorks) { + auto kValue = + "République is a French-inspired bakery, café, bar and formal dining room located in the Mid-city neighborhood of Los Angeles, set in a historic 1928 space ..."_ns; + mozilla::glean::test_only_ipc::a_text.Set(kValue); + ASSERT_STREQ(kValue.get(), + mozilla::glean::test_only_ipc::a_text.TestGetValue() + .unwrap() + .value() + .get()); +} + +extern "C" void Rust_TestRustInGTest(); +TEST_F(FOGFixture, TestRustInGTest) { Rust_TestRustInGTest(); } + +extern "C" void Rust_TestJogfile(); +TEST_F(FOGFixture, TestJogfile) { Rust_TestJogfile(); } diff --git a/toolkit/components/glean/tests/gtest/moz.build b/toolkit/components/glean/tests/gtest/moz.build new file mode 100644 index 0000000000..d17ee0e0dd --- /dev/null +++ b/toolkit/components/glean/tests/gtest/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if not CONFIG["MOZ_GLEAN_ANDROID"]: + UNIFIED_SOURCES += [ + "TestFog.cpp", + ] + + TEST_HARNESS_FILES.gtest += ["../pytest/jogfile_output"] + +FINAL_LIBRARY = "xul-gtest" diff --git a/toolkit/components/glean/tests/gtest/test.rs b/toolkit/components/glean/tests/gtest/test.rs new file mode 100644 index 0000000000..90ad67aebb --- /dev/null +++ b/toolkit/components/glean/tests/gtest/test.rs @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +extern crate firefox_on_glean; +use firefox_on_glean::metrics; + +extern crate nsstring; +use nsstring::nsString; + +fn nonfatal_fail(msg: String) { + extern "C" { + fn GTest_FOG_ExpectFailure(message: *const ::std::os::raw::c_char); + } + unsafe { + let msg = ::std::ffi::CString::new(msg).unwrap(); + GTest_FOG_ExpectFailure(msg.as_ptr()); + } +} + +/// This macro checks if the expression evaluates to true, +/// and causes a non-fatal GTest test failure if it doesn't. +macro_rules! expect { + ($x:expr) => { + match (&$x) { + true => {} + false => nonfatal_fail(format!( + "check failed: (`{}`) at {}:{}", + stringify!($x), + file!(), + line!() + )), + } + }; +} + +#[no_mangle] +pub extern "C" fn Rust_TestRustInGTest() { + // Just a smoke test, we show here how tests might work that both + // a) Are in Rust, and + // b) Require Gecko + // This demonstration doesn't actually require Gecko. But we pretend it + // does so we remember how to do this rust-in-gtest pattern. + metrics::test_only::bad_code.add(12); + expect!(metrics::test_only::bad_code.test_get_value(None) == Some(12)); +} + +#[no_mangle] +pub extern "C" fn Rust_TestJogfile() { + // Ensure that the generated jogfile in t/c/g/tests/pytest + // (which is installed nearby using TEST_HARNESS_FILES) + // can be consumed by JOG's inner workings + // + // If it can't, that's perhaps a sign that the inner workings need to be updated. + expect!(jog::jog_load_jogfile(&nsString::from("jogfile_output"))); +} |