Add force-test-environment flag and checks

Add --force-test-environment flag to test_main.
This flag sets a global boolean that CreateEnvironment
and FieldTrials will check, crashing if they are used
in tests instead of the test-specific versions.

Generated by prompt:

We want a way to detect if CreateEnvironment or CreateFieldTrials is
used in tests where we should be using CreateTestEnvironment or
CreateTestFieldTrials. Add code to test/test_main.cc to have a
command line flag '--force-test-environment' that sets a global
boolean that CreateEnvironment and CreateFieldTrials will RTC_CHECK on
so that we immediately crash if the non-test versions are used.

Bug: webrtc:521534686
Change-Id: I7f47b4ccfc396f90d7626d430140e92834b6a483
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/480160
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47947}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 09b6816..2e0bd76 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -1808,6 +1808,7 @@
       "../test:test_support",
       "audio_codecs/opus:unittests",
       "environment:environment_unittests",
+      "environment:force_test_environment",
       "task_queue:task_queue_default_factory_unittests",
       "test/pclf:media_configuration",
       "test/video:video_frame_writer",
@@ -1919,6 +1920,7 @@
     ":field_trials_view",
     "../rtc_base:checks",
     "../rtc_base/containers:flat_map",
+    "environment:force_test_environment",
     "//third_party/abseil-cpp/absl/base:nullability",
     "//third_party/abseil-cpp/absl/memory",
     "//third_party/abseil-cpp/absl/strings:string_view",
diff --git a/api/environment/BUILD.gn b/api/environment/BUILD.gn
index b28c754..e4455b0 100644
--- a/api/environment/BUILD.gn
+++ b/api/environment/BUILD.gn
@@ -8,6 +8,14 @@
 
 import("../../webrtc.gni")
 
+rtc_library("force_test_environment") {
+  visibility = webrtc_default_visibility
+  sources = [
+    "force_test_environment.cc",
+    "force_test_environment.h",
+  ]
+}
+
 rtc_source_set("environment") {
   visibility = [ "*" ]
   sources = [ "environment.h" ]
@@ -52,6 +60,7 @@
   deps = [
     ":deprecated_global_field_trials",
     ":environment",
+    ":force_test_environment",
     "..:field_trials_view",
     "..:make_ref_counted",
     "..:refcountedbase",
@@ -73,8 +82,10 @@
     deps = [
       ":environment",
       ":environment_factory",
+      ":force_test_environment",
       "..:field_trials_view",
       "../../system_wrappers",
+      "../../test:create_test_environment",
       "../../test:test_support",
       "../rtc_event_log",
       "../task_queue",
diff --git a/api/environment/environment_factory.cc b/api/environment/environment_factory.cc
index 529bbec..1f2f31d 100644
--- a/api/environment/environment_factory.cc
+++ b/api/environment/environment_factory.cc
@@ -16,6 +16,7 @@
 #include "absl/base/nullability.h"
 #include "api/environment/deprecated_global_field_trials.h"
 #include "api/environment/environment.h"
+#include "api/environment/force_test_environment.h"
 #include "api/field_trials_view.h"
 #include "api/make_ref_counted.h"
 #include "api/ref_counted_base.h"
@@ -99,6 +100,10 @@
 }
 
 Environment EnvironmentFactory::CreateWithDefaults() && {
+  RTC_CHECK(!IsForceTestEnvironmentEnabled() ||
+            IsTestEnvironmentCheckBypassed())
+      << "Production Environment creation is not allowed in tests. Use "
+         "CreateTestEnvironment.";
   if (field_trials_ == nullptr) {
     Set(std::make_unique<DeprecatedGlobalFieldTrials>());
   }
diff --git a/api/environment/environment_unittest.cc b/api/environment/environment_unittest.cc
index 048f250..314283a 100644
--- a/api/environment/environment_unittest.cc
+++ b/api/environment/environment_unittest.cc
@@ -19,6 +19,7 @@
 #include "absl/functional/any_invocable.h"
 #include "absl/strings/string_view.h"
 #include "api/environment/environment_factory.h"
+#include "api/environment/force_test_environment.h"
 #include "api/field_trials_view.h"
 #include "api/rtc_event_log/rtc_event.h"
 #include "api/rtc_event_log/rtc_event_log.h"
@@ -26,6 +27,7 @@
 #include "api/task_queue/task_queue_factory.h"
 #include "api/units/timestamp.h"
 #include "system_wrappers/include/clock.h"
+#include "test/create_test_environment.h"
 #include "test/gmock.h"
 #include "test/gtest.h"
 
@@ -280,5 +282,36 @@
   EXPECT_THAT(destroyed, ElementsAre("task_queue_factory", "field_trials"));
 }
 
+TEST(EnvironmentTest, CreateTestEnvironmentWorksWhenForced) {
+  struct ScopedForce {
+    ScopedForce() {
+      old_value = IsForceTestEnvironmentEnabled();
+      SetForceTestEnvironment(true);
+    }
+    ~ScopedForce() { SetForceTestEnvironment(old_value); }
+    bool old_value;
+  } force;
+
+  Environment env = CreateTestEnvironment();
+  env.clock().CurrentTime();
+  EXPECT_THAT(env.task_queue_factory().CreateTaskQueue(
+                  "test", TaskQueueFactory::Priority::kNormal),
+              NotNull());
+  env.event_log().Log(std::make_unique<FakeEvent>());
+  env.field_trials().Lookup("WebRTC-Debugging-RtpDump");
+}
+
+#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+TEST(EnvironmentDeathTest, CreateEnvironmentCrashesWhenForced) {
+  EXPECT_DEATH(
+      {
+        SetForceTestEnvironment(true);
+        CreateEnvironment();
+      },
+      "Production Environment creation is not allowed in tests. Use "
+      "CreateTestEnvironment.");
+}
+#endif
+
 }  // namespace
 }  // namespace webrtc
diff --git a/api/environment/force_test_environment.cc b/api/environment/force_test_environment.cc
new file mode 100644
index 0000000..df2de40
--- /dev/null
+++ b/api/environment/force_test_environment.cc
@@ -0,0 +1,43 @@
+/*
+ *  Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/environment/force_test_environment.h"
+
+#include <atomic>
+
+namespace webrtc {
+namespace {
+
+std::atomic<bool> g_force_test_environment{false};
+thread_local int g_bypass_test_environment_check_count = 0;
+
+}  // namespace
+
+void SetForceTestEnvironment(bool force) {
+  g_force_test_environment.store(force, std::memory_order_relaxed);
+}
+
+bool IsForceTestEnvironmentEnabled() {
+  return g_force_test_environment.load(std::memory_order_relaxed);
+}
+
+AutoBypassTestEnvironmentCheck::AutoBypassTestEnvironmentCheck() {
+  ++g_bypass_test_environment_check_count;
+}
+
+AutoBypassTestEnvironmentCheck::~AutoBypassTestEnvironmentCheck() {
+  --g_bypass_test_environment_check_count;
+}
+
+bool IsTestEnvironmentCheckBypassed() {
+  return g_bypass_test_environment_check_count > 0;
+}
+
+}  // namespace webrtc
diff --git a/api/environment/force_test_environment.h b/api/environment/force_test_environment.h
new file mode 100644
index 0000000..c6fdae7
--- /dev/null
+++ b/api/environment/force_test_environment.h
@@ -0,0 +1,38 @@
+/*
+ *  Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_ENVIRONMENT_FORCE_TEST_ENVIRONMENT_H_
+#define API_ENVIRONMENT_FORCE_TEST_ENVIRONMENT_H_
+
+namespace webrtc {
+
+// Sets flag to crash if non-test Environment or FieldTrials are created.
+void SetForceTestEnvironment(bool force);
+
+// Returns true if force-test-environment is enabled.
+bool IsForceTestEnvironmentEnabled();
+
+// RAII class to temporarily bypass the check.
+class AutoBypassTestEnvironmentCheck {
+ public:
+  AutoBypassTestEnvironmentCheck();
+  ~AutoBypassTestEnvironmentCheck();
+  AutoBypassTestEnvironmentCheck(const AutoBypassTestEnvironmentCheck&) =
+      delete;
+  AutoBypassTestEnvironmentCheck& operator=(
+      const AutoBypassTestEnvironmentCheck&) = delete;
+};
+
+// Returns true if the check should be bypassed.
+bool IsTestEnvironmentCheckBypassed();
+
+}  // namespace webrtc
+
+#endif  // API_ENVIRONMENT_FORCE_TEST_ENVIRONMENT_H_
diff --git a/api/field_trials.cc b/api/field_trials.cc
index a7f0527..c243598 100644
--- a/api/field_trials.cc
+++ b/api/field_trials.cc
@@ -17,6 +17,7 @@
 #include "absl/base/nullability.h"
 #include "absl/memory/memory.h"
 #include "absl/strings/string_view.h"
+#include "api/environment/force_test_environment.h"
 #include "api/field_trials_registry.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/containers/flat_map.h"
@@ -57,6 +58,10 @@
 
 absl_nullable std::unique_ptr<FieldTrials> FieldTrials::Create(
     absl::string_view s) {
+  RTC_CHECK(!IsForceTestEnvironmentEnabled() ||
+            IsTestEnvironmentCheckBypassed())
+      << "FieldTrials::Create is not allowed in tests. Use "
+         "CreateTestFieldTrials.";
   flat_map<std::string, std::string> key_value_map;
   if (!Parse(s, key_value_map)) {
     return nullptr;
@@ -66,6 +71,10 @@
 }
 
 FieldTrials::FieldTrials(absl::string_view s) {
+  RTC_CHECK(!IsForceTestEnvironmentEnabled() ||
+            IsTestEnvironmentCheckBypassed())
+      << "FieldTrials constructor is not allowed in tests. Use "
+         "CreateTestFieldTrials.";
   RTC_CHECK(Parse(s, key_value_map_));
 }
 
diff --git a/api/field_trials_unittest.cc b/api/field_trials_unittest.cc
index ba44d6e..d86451d 100644
--- a/api/field_trials_unittest.cc
+++ b/api/field_trials_unittest.cc
@@ -13,6 +13,7 @@
 #include <memory>
 
 #include "absl/strings/str_cat.h"
+#include "api/environment/force_test_environment.h"
 #include "api/field_trials_view.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/containers/flat_set.h"
@@ -188,5 +189,25 @@
 #endif
 }
 
+#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+TEST(FieldTrialsDeathTest, CreateCrashesWhenForced) {
+  EXPECT_DEATH(
+      {
+        SetForceTestEnvironment(true);
+        FieldTrials::Create("");
+      },
+      "FieldTrials::Create is not allowed in tests");
+}
+
+TEST(FieldTrialsDeathTest, ConstructorCrashesWhenForced) {
+  EXPECT_DEATH(
+      {
+        SetForceTestEnvironment(true);
+        FieldTrials f("");
+      },
+      "FieldTrials constructor is not allowed in tests");
+}
+#endif
+
 }  // namespace
 }  // namespace webrtc
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 1f39c7d8..9fb3850 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -161,6 +161,7 @@
     "../api:time_controller",
     "../api/environment",
     "../api/environment:environment_factory",
+    "../api/environment:force_test_environment",
     "../api/rtc_event_log",
     "../rtc_base:checks",
     "../system_wrappers",
@@ -174,6 +175,7 @@
   sources = [ "create_test_field_trials.h" ]
   deps = [
     "../api:field_trials",
+    "../api/environment:force_test_environment",
     "//third_party/abseil-cpp/absl/base:nullability",
     "//third_party/abseil-cpp/absl/strings:string_view",
   ]
@@ -641,8 +643,10 @@
       deps = [
         ":test_main_lib",
         ":test_support",
+        "../api/environment:force_test_environment",
         "//third_party/abseil-cpp/absl/debugging:failure_signal_handler",
         "//third_party/abseil-cpp/absl/debugging:symbolize",
+        "//third_party/abseil-cpp/absl/flags:flag",
         "//third_party/abseil-cpp/absl/flags:parse",
       ]
     }
@@ -720,6 +724,7 @@
         ":create_test_field_trials",
         ":test_support",
         "../api:field_trials",
+        "../api/environment:force_test_environment",
         "//third_party/abseil-cpp/absl/flags:flag",
         "//third_party/abseil-cpp/absl/strings:string_view",
       ]
diff --git a/test/create_test_environment.cc b/test/create_test_environment.cc
index 030c968..741fe96 100644
--- a/test/create_test_environment.cc
+++ b/test/create_test_environment.cc
@@ -18,6 +18,7 @@
 #include "absl/strings/string_view.h"
 #include "api/environment/environment.h"
 #include "api/environment/environment_factory.h"
+#include "api/environment/force_test_environment.h"
 #include "api/field_trials.h"
 #include "api/field_trials_view.h"
 #include "api/test/time_controller.h"
@@ -66,6 +67,7 @@
 }  // namespace
 
 Environment CreateTestEnvironment(CreateTestEnvironmentOptions o) {
+  AutoBypassTestEnvironmentCheck bypass;
   EnvironmentFactory factory;
 
   std::visit(SetFieldTrials{.factory = factory}, std::move(o.field_trials));
diff --git a/test/create_test_field_trials.cc b/test/create_test_field_trials.cc
index c1644f4..59524ca 100644
--- a/test/create_test_field_trials.cc
+++ b/test/create_test_field_trials.cc
@@ -14,6 +14,7 @@
 
 #include "absl/flags/flag.h"
 #include "absl/strings/string_view.h"
+#include "api/environment/force_test_environment.h"
 #include "api/field_trials.h"
 
 ABSL_FLAG(std::string,
@@ -26,6 +27,7 @@
 namespace webrtc {
 
 FieldTrials CreateTestFieldTrials(absl::string_view s) {
+  AutoBypassTestEnvironmentCheck bypass;
   FieldTrials result(absl::GetFlag(FLAGS_force_fieldtrials));
   result.Merge(FieldTrials(s));
   return result;
diff --git a/test/create_test_field_trials_unittest.cc b/test/create_test_field_trials_unittest.cc
index 661cc06..a125af0 100644
--- a/test/create_test_field_trials_unittest.cc
+++ b/test/create_test_field_trials_unittest.cc
@@ -15,6 +15,7 @@
 #include "absl/flags/declare.h"
 #include "absl/flags/flag.h"
 #include "absl/strings/string_view.h"
+#include "api/environment/force_test_environment.h"
 #include "api/field_trials.h"
 #include "test/gtest.h"
 
@@ -72,5 +73,20 @@
   EXPECT_EQ(field_trials.Lookup("TrialConstructor"), "ConstructorValue");
 }
 
+TEST(CreateTestFieldTrialsTest, WorksWhenForceTestEnvironmentEnabled) {
+  struct ScopedForce {
+    ScopedForce() {
+      old_value = IsForceTestEnvironmentEnabled();
+      SetForceTestEnvironment(true);
+    }
+    ~ScopedForce() { SetForceTestEnvironment(old_value); }
+    bool old_value;
+  } force;
+
+  FieldTrials field_trials = CreateTestFieldTrials("Trial/Value/");
+  field_trials.RegisterKeysForTesting({"Trial"});
+  EXPECT_EQ(field_trials.Lookup("Trial"), "Value");
+}
+
 }  // namespace
 }  // namespace webrtc
diff --git a/test/create_test_field_trials_without_absl_flag.cc b/test/create_test_field_trials_without_absl_flag.cc
index f14f8ed..7768796 100644
--- a/test/create_test_field_trials_without_absl_flag.cc
+++ b/test/create_test_field_trials_without_absl_flag.cc
@@ -13,12 +13,14 @@
 // the command line flag by default.
 
 #include "absl/strings/string_view.h"
+#include "api/environment/force_test_environment.h"
 #include "api/field_trials.h"
 #include "test/create_test_field_trials.h"
 
 namespace webrtc {
 
 FieldTrials CreateTestFieldTrials(absl::string_view s) {
+  AutoBypassTestEnvironmentCheck bypass;
   return FieldTrials(s);
 }
 
diff --git a/test/test_main.cc b/test/test_main.cc
index 9dcc57c..ca49dcb 100644
--- a/test/test_main.cc
+++ b/test/test_main.cc
@@ -16,10 +16,17 @@
 
 #include "absl/debugging/failure_signal_handler.h"
 #include "absl/debugging/symbolize.h"
+#include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
+#include "api/environment/force_test_environment.h"
 #include "test/gmock.h"
 #include "test/test_main_lib.h"
 
+ABSL_FLAG(bool,
+          force_test_environment,
+          false,
+          "Crash if non-test Environment or FieldTrials are created.");
+
 namespace {
 
 std::vector<std::string> ReplaceDashesWithUnderscores(int argc, char* argv[]) {
@@ -58,6 +65,7 @@
   std::vector<std::string> new_argv = ReplaceDashesWithUnderscores(argc, argv);
   std::vector<char*> raw_new_argv = VectorOfStringsToVectorOfPointers(new_argv);
   absl::ParseCommandLine(argc, &raw_new_argv[0]);
+  webrtc::SetForceTestEnvironment(absl::GetFlag(FLAGS_force_test_environment));
 
 // This absl handler use unsupported features/instructions on Fuchsia
 #if !defined(WEBRTC_FUCHSIA)