Open source one version tests

(ignore-relnotes)

PiperOrigin-RevId: 910688799
Change-Id: Ifa9ae668c58137ff14eb2aa1a2875c8ac5fe0b37
diff --git a/java/common/BUILD b/java/common/BUILD
index 9881236..7887b1f 100644
--- a/java/common/BUILD
+++ b/java/common/BUILD
@@ -36,7 +36,10 @@
     name = "semantics_bzl",
     srcs = ["java_semantics.bzl"],
     visibility = ["//visibility:public"],
-    deps = ["@rules_cc//cc/common:cc_helper_bzl"],
+    deps = [
+        "@bazel_features//:features",
+        "@rules_cc//cc/common:cc_helper_bzl",
+    ],
 )
 
 bzl_library(
diff --git a/java/common/java_semantics.bzl b/java/common/java_semantics.bzl
index e847d87..919553e 100644
--- a/java/common/java_semantics.bzl
+++ b/java/common/java_semantics.bzl
@@ -13,6 +13,7 @@
 # limitations under the License.
 """Bazel Java Semantics"""
 
+load("@bazel_features//private:util.bzl", _bazel_version_ge = "ge")
 load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
 load("@rules_cc//cc/common:cc_helper.bzl", "cc_helper")
 
@@ -126,4 +127,5 @@
     INCOMPATIBLE_DISABLE_NON_EXECUTABLE_JAVA_BINARY = False,  # Flip when java_single_jar is feature complete
     update_args_for_import_deps = _update_args_for_import_deps,
     expand_javacopts_make_variables = True,
+    java_toolchain_supports_one_version = _bazel_version_ge("8.0.0"),  # can be dropped once we no longer support Bazel 7
 )
diff --git a/test/java/common/rules/java_binary_tests.bzl b/test/java/common/rules/java_binary_tests.bzl
index 7ee9eb1..f2ab5be 100644
--- a/test/java/common/rules/java_binary_tests.bzl
+++ b/test/java/common/rules/java_binary_tests.bzl
@@ -1,6 +1,7 @@
 """Tests for the java_binary rule"""
 
 load("@bazel_features//:features.bzl", "bazel_features")
+load("@bazel_skylib//lib:paths.bzl", "paths")
 load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
 load("@rules_cc//cc:cc_library.bzl", "cc_library")
 load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
@@ -8,9 +9,12 @@
 load("@rules_testing//lib:util.bzl", "util")
 load("//java:java_binary.bzl", "java_binary")
 load("//java:java_library.bzl", "java_library")
+load("//java/common:java_semantics.bzl", "semantics")
+load("//java/common/rules:java_helper.bzl", "helper")
 load("//test/java/common/rules:common_launcher_java_binary_tests.bzl", "JAVA_BINARY_LAUNCHER_TESTS")
 load("//test/java/testutil:helper.bzl", "always_passes")
 load("//test/java/testutil:java_info_subject.bzl", "java_info_subject")
+load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_toolchain")
 load("//test/java/testutil:rules/custom_java_info_rule.bzl", "custom_java_info_rule")
 load("//test/java/testutil:rules/forward_java_info.bzl", "java_info_forwarding_rule")
 
@@ -250,6 +254,158 @@
     # ensure they don't propagate to `bin` because they're a part of the `deploy_env`
     env.expect.that_target(targets.bin).output_group("_validation").contains_exactly([])
 
+def _test_one_version_check_action(name):
+    if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags:
+        # exit early because this test case would be a loading phase error otherwise
+        always_passes(name)
+        return
+
+    util.helper_target(
+        java_library,
+        name = name + "/c",
+        srcs = [name + "/c.java"],
+    )
+    util.helper_target(
+        java_library,
+        name = name + "/a",
+        srcs = [name + "/a.java"],
+        deps = [name + "/c"],
+    )
+    util.helper_target(
+        java_binary,
+        name = name + "/b",
+        srcs = [name + "/b.java"],
+        deps = [name + "/a"],
+    )
+    util.helper_target(
+        mock_java_toolchain,
+        name = name + "/toolchain",
+        oneversion = "one_version_tool",
+        oneversion_allowlist = "one_version_allowlist",
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_one_version_check_action_impl,
+        target = name + "/b",
+        config_settings = {
+            "//command_line_option:experimental_one_version_enforcement": "ERROR",
+            "//command_line_option:extra_toolchains": [Label(name + "/toolchain")],
+        },
+        attrs = {
+            "_windows_constraints": attr.label_list(
+                default = [paths.join(semantics.PLATFORMS_ROOT, "os:windows")],
+            ),
+        },
+    )
+
+def _test_one_version_check_action_impl(env, target):
+    assert_target = env.expect.that_target(target)
+    assert_target.default_outputs().contains_exactly([
+        "{package}/{test_name}/b.jar",
+        "{package}/{test_name}/b" + (".exe" if helper.is_target_platform_windows(env.ctx) else ""),
+    ])
+    assert_target.output_group("_validation").contains(
+        "{package}/{name}-one-version.txt",
+    )
+    assert_action = assert_target.action_generating("{package}/{name}-one-version.txt")
+    assert_action.mnemonic().equals("JavaOneVersion")
+    assert_action.inputs().contains_at_least([
+        "{package}/{test_name}/b.jar",
+        "{package}/lib{test_name}/a.jar",
+        "{package}/lib{test_name}/c.jar",
+    ])
+    tool = [f for f in assert_action.actual.inputs.to_list() if f.short_path.endswith("one_version_tool")][0]
+    assert_action.argv().contains_exactly([
+        tool.path,
+        "--output",
+        "{bindir}/{package}/{name}-one-version.txt",
+        "--allowlist",
+        "{package}/one_version_allowlist",
+        "--inputs",
+        "{bindir}/{package}/{test_name}/b.jar,//{package}:{test_name}/b",
+        "{bindir}/{package}/lib{test_name}/a.jar,//{package}:{test_name}/a",
+        "{bindir}/{package}/lib{test_name}/c.jar,//{package}:{test_name}/c",
+    ]).in_order()
+
+def _test_one_version_check_violations_allowed(name):
+    if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags:
+        # exit early because this test case would be a loading phase error otherwise
+        always_passes(name)
+        return
+
+    util.helper_target(
+        java_binary,
+        name = name + "/foo",
+        srcs = [name + "/foo.java"],
+    )
+    util.helper_target(
+        mock_java_toolchain,
+        name = name + "/toolchain",
+        oneversion = "one_version_tool",
+        oneversion_allowlist = "one_version_allowlist",
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_one_version_check_violations_allowed_impl,
+        target = name + "/foo",
+        config_settings = {
+            "//command_line_option:experimental_one_version_enforcement": "WARNING",
+            "//command_line_option:extra_toolchains": [Label(name + "/toolchain")],
+        },
+    )
+
+def _test_one_version_check_violations_allowed_impl(env, target):
+    assert_action = env.expect.that_target(target).action_generating(
+        "{package}/{name}-one-version.txt",
+    )
+    env.expect.that_target(target).output_group("_validation").contains(
+        "{package}/{name}-one-version.txt",
+    )
+    tool = [f for f in assert_action.actual.inputs.to_list() if f.short_path.endswith("one_version_tool")][0]
+    assert_action.argv().contains_exactly([
+        tool.path,
+        "--output",
+        "{bindir}/{package}/{name}-one-version.txt",
+        "--allowlist",
+        "{package}/one_version_allowlist",
+        "--succeed_on_found_violations",
+        "--inputs",
+        "{bindir}/{package}/{test_name}/foo.jar,//{package}:{test_name}/foo",
+    ]).in_order()
+
+def _test_one_version_check_disabled(name):
+    if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags:
+        # exit early because this test case would be a loading phase error otherwise
+        always_passes(name)
+        return
+
+    util.helper_target(
+        java_binary,
+        name = name + "/foo",
+        srcs = [name + "/foo.java"],
+    )
+    util.helper_target(
+        mock_java_toolchain,
+        name = name + "/toolchain",
+        oneversion = "one_version_tool",
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_one_version_check_disabled_impl,
+        target = name + "/foo",
+        config_settings = {
+            "//command_line_option:experimental_one_version_enforcement": "OFF",
+            "//command_line_option:extra_toolchains": [Label(name + "/toolchain")],
+        },
+    )
+
+def _test_one_version_check_disabled_impl(env, target):
+    action_mnemonics = [a.mnemonic for a in env.expect.that_target(target).actual.actions]
+    env.expect.that_collection(action_mnemonics).not_contains("JavaOneVersion")
+
 def java_binary_tests(name):
     test_suite(
         name = "_basic_" + name,
@@ -260,6 +416,9 @@
             _test_java_binary_propagates_direct_native_libraries,
             _test_java_compile_only,
             _test_java_binary_can_set_transitive_validation,
+            _test_one_version_check_action,
+            _test_one_version_check_violations_allowed,
+            _test_one_version_check_disabled,
         ],
     )
 
diff --git a/test/java/common/rules/java_test_tests.bzl b/test/java/common/rules/java_test_tests.bzl
index 6c71d33..995872f 100644
--- a/test/java/common/rules/java_test_tests.bzl
+++ b/test/java/common/rules/java_test_tests.bzl
@@ -1,18 +1,21 @@
 """Tests for the java_test rule"""
 
 load("@bazel_features//:features.bzl", "bazel_features")
+load("@bazel_skylib//lib:paths.bzl", "paths")
 load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
 load("@rules_cc//cc:cc_library.bzl", "cc_library")
 load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
 load("@rules_testing//lib:truth.bzl", "matching", "subjects")
 load("@rules_testing//lib:util.bzl", "util")
+load("//java:java_binary.bzl", "java_binary")
 load("//java:java_library.bzl", "java_library")
 load("//java:java_test.bzl", "java_test")
 load("//java/common:java_info.bzl", "JavaInfo")
 load("//java/common:java_semantics.bzl", "semantics")
+load("//java/common/rules:java_helper.bzl", "helper")
 load("//test/java/testutil:helper.bzl", "always_passes")
 load("//test/java/testutil:mock_cc_toolchain.bzl", "mock_cc_toolchain")
-load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_runtime_toolchain")
+load("//test/java/testutil:mock_java_toolchain.bzl", "mock_java_runtime_toolchain", "mock_java_toolchain")
 load("//test/java/testutil:mock_test_toolchain.bzl", "mock_test_toolchains")
 load("//test/java/testutil:rules/custom_java_info_rule.bzl", "custom_java_info_rule")
 
@@ -326,6 +329,111 @@
             matching.str_matches("-Djava.security.manager=allow"),
         )
 
+def _test_one_version_check_java_test(name):
+    if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags:
+        # exit early because this test case would be a loading phase error otherwise
+        always_passes(name)
+        return
+
+    util.helper_target(
+        java_library,
+        name = name + "/foo",
+        srcs = [name + "/foo.java"],
+    )
+    util.helper_target(
+        java_test,
+        name = name + "/foo_test",
+        srcs = [name + "/foo_test.java"],
+        deps = [name + "/foo"],
+        use_testrunner = False,
+    )
+    util.helper_target(
+        mock_java_toolchain,
+        name = name + "/toolchain",
+        oneversion = "one_version_tool",
+        oneversion_allowlist = "one_version_allowlist",
+        oneversion_allowlist_for_tests = "one_version_allowlist_for_tests",
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_one_version_check_java_test_impl,
+        target = name + "/foo_test",
+        config_settings = {
+            "//command_line_option:experimental_one_version_enforcement": "ERROR",
+            "//command_line_option:extra_toolchains": [Label(name + "/toolchain")],
+        },
+        attrs = {
+            "_windows_constraints": attr.label_list(
+                default = [paths.join(semantics.PLATFORMS_ROOT, "os:windows")],
+            ),
+        },
+    )
+
+def _test_one_version_check_java_test_impl(env, target):
+    assert_target = env.expect.that_target(target)
+    assert_target.default_outputs().contains_exactly([
+        "{package}/{test_name}/foo_test.jar",
+        "{package}/{test_name}/foo_test" + (".exe" if helper.is_target_platform_windows(env.ctx) else ""),
+    ])
+    assert_action = assert_target.action_generating(
+        "{package}/{name}-one-version.txt",
+    )
+    tool = [f for f in assert_action.actual.inputs.to_list() if f.short_path.endswith("one_version_tool")][0]
+    assert_action.argv().contains_exactly([
+        tool.path,
+        "--output",
+        "{bindir}/{package}/{name}-one-version.txt",
+        "--allowlist",
+        "{package}/one_version_allowlist_for_tests",
+        "--inputs",
+        "{bindir}/{package}/{test_name}/foo_test.jar,//{package}:{test_name}/foo_test",
+        "{bindir}/{package}/lib{test_name}/foo.jar,//{package}:{test_name}/foo",
+    ]).in_order()
+
+def _test_one_version_check_disabled_for_java_test(name):
+    if not bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags:
+        # exit early because this test case would be a loading phase error otherwise
+        always_passes(name)
+        return
+
+    util.helper_target(
+        java_test,
+        name = name + "/foo_test",
+        srcs = [name + "/foo.java"],
+        use_testrunner = False,
+    )
+    util.helper_target(
+        java_binary,
+        name = name + "/foo_binary",
+        srcs = [name + "/foo.java"],
+    )
+    util.helper_target(
+        mock_java_toolchain,
+        name = name + "/toolchain",
+        oneversion = "one_version_tool",
+    )
+
+    analysis_test(
+        name = name,
+        impl = _test_one_version_check_disabled_for_java_test_impl,
+        targets = {
+            "bin": name + "/foo_binary",
+            "test": name + "/foo_test",
+        },
+        config_settings = {
+            "//command_line_option:experimental_one_version_enforcement": "ERROR",
+            "//command_line_option:one_version_enforcement_on_java_tests": False,
+            "//command_line_option:extra_toolchains": [Label(name + "/toolchain")],
+        },
+    )
+
+def _test_one_version_check_disabled_for_java_test_impl(env, targets):
+    binary_action_mnemonics = [a.mnemonic for a in env.expect.that_target(targets.bin).actual.actions]
+    test_action_mnemonics = [a.mnemonic for a in env.expect.that_target(targets.test).actual.actions]
+    env.expect.that_collection(binary_action_mnemonics).contains("JavaOneVersion")
+    env.expect.that_collection(test_action_mnemonics).not_contains("JavaOneVersion")
+
 def java_test_tests(name):
     test_suite(
         name = name,
@@ -338,5 +446,7 @@
             _test_add_test_support_to_compile_time_deps_flag,
             _test_mac_requires_darwin_for_execution,
             _test_java_test_sets_securiry_manager_property_jdk17,
+            _test_one_version_check_java_test,
+            _test_one_version_check_disabled_for_java_test,
         ],
     )
diff --git a/test/java/testutil/mock_java_toolchain.bzl b/test/java/testutil/mock_java_toolchain.bzl
index de824b1..aae0afd 100644
--- a/test/java/testutil/mock_java_toolchain.bzl
+++ b/test/java/testutil/mock_java_toolchain.bzl
@@ -15,11 +15,19 @@
         ijar = "ijar",
         genclass = "genclass",
         java_runtime = None,
+        oneversion = None,
+        oneversion_allowlist = None,
+        oneversion_allowlist_for_tests = None,
         tags = None,  # for util.helper_target
         **kwargs):
     if not java_runtime:
         java_runtime = name + "_runtime"
         _java_runtime_rule(name = java_runtime)
+    one_version_args = {
+        "oneversion": oneversion,
+        "oneversion_allowlist": oneversion_allowlist,
+        "oneversion_allowlist_for_tests": oneversion_allowlist_for_tests,
+    } if semantics.java_toolchain_supports_one_version else {}
     java_toolchain(
         name = name + "_java",
         javabuilder = javabuilder,
@@ -30,7 +38,7 @@
         java_runtime = java_runtime,
         genclass = genclass,
         tags = tags,
-        **kwargs
+        **(kwargs | one_version_args)
     )
     native.toolchain(
         name = name,