rule_test: apply "tags" to all rules in the macro

RELNOTES: rule_test: fix Bazel 0.27 regression ("tags" attribute was ingored, https://github.com/bazelbuild/bazel/issues/8723

Fixes https://github.com/bazelbuild/bazel/issues/8723

Change-Id: I25ac338e4978084085b8c49d6d0a1c47d8dc4fd1

Closes #8784.

Change-Id: Ie53b8787ab8679ff4896820ef552c2696eac1d09
PiperOrigin-RevId: 256509917
diff --git a/src/test/shell/bazel/rule_test_test.sh b/src/test/shell/bazel/rule_test_test.sh
index 2fb41a6..195dc2b 100755
--- a/src/test/shell/bazel/rule_test_test.sh
+++ b/src/test/shell/bazel/rule_test_test.sh
@@ -175,4 +175,59 @@
   bazel build //:turtle_rule_test &> $TEST_log || fail "turtle_rule_test failed"
 }
 
+# Regression test for https://github.com/bazelbuild/bazel/issues/8723
+#
+# rule_test() is a macro that expands to a sh_test and _rule_test_rule.
+# Expect that:
+# * test- and build-rule attributes (e.g. "tags") are applied to both rules,
+# * test-only attributes are applied only to the sh_rule,
+# * the build rule has its own visibility
+function test_kwargs_with_macro_rules() {
+  create_new_workspace
+  cat > BUILD <<'EOF'
+load("@bazel_tools//tools/build_rules:test_rules.bzl", "rule_test")
+
+genrule(
+    name = "x",
+    srcs = ["@does_not_exist//:bad"],
+    outs = ["x.out"],
+    cmd = "touch $@",
+    tags = ["dont_build_me"],
+)
+
+rule_test(
+    name = "x_test",
+    rule = "//:x",
+    generates = ["x.out"],
+    visibility = ["//foo:__pkg__"],
+    tags = ["dont_build_me"],
+    args = ["x"],
+    flaky = False,
+    local = True,
+    shard_count = 2,
+    size = "small",
+    timeout = "short",
+)
+EOF
+
+  bazel build //:all >& "$TEST_log" && fail "should have failed" || true
+
+  bazel build --build_tag_filters=-dont_build_me //:all >& "$TEST_log" || fail "build failed"
+
+  bazel query --output=label 'attr(tags, dont_build_me, //:all)' >& "$TEST_log" || fail "query failed"
+  expect_log '//:x_test_impl'
+  expect_log '//:x_test\b'
+  expect_log '//:x\b'
+
+  bazel query --output=label 'attr(visibility, private, //:all)' >& "$TEST_log" || fail "query failed"
+  expect_log '//:x_test_impl'
+  expect_not_log '//:x_test\b'
+  expect_not_log '//:x\b'
+
+  bazel query --output=label 'attr(visibility, foo, //:all)' >& "$TEST_log" || fail "query failed"
+  expect_log '//:x_test\b'
+  expect_not_log '//:x_test_impl'
+  expect_not_log '//:x\b'
+}
+
 run_suite "rule_test tests"
diff --git a/tools/build_rules/test_rules.bzl b/tools/build_rules/test_rules.bzl
index 53079cb..ef26b8f 100644
--- a/tools/build_rules/test_rules.bzl
+++ b/tools/build_rules/test_rules.bzl
@@ -35,6 +35,27 @@
         **kwargs
     )
 
+_TEST_ATTRS = {
+    "args": None,
+    "size": None,
+    "timeout": None,
+    "flaky": None,
+    "local": None,
+    "shard_count": None,
+}
+
+def _helper_rule_attrs(test_attrs, own_attrs):
+    r = {}
+    r.update({k: v for k, v in test_attrs.items() if k not in _TEST_ATTRS})
+    r.update(own_attrs)
+    r.update(
+        dict(
+            testonly = 1,
+            visibility = ["//visibility:private"],
+        ),
+    )
+    return r
+
 ### First, trivial tests that either always pass, always fail,
 ### or sometimes pass depending on a trivial computation.
 
@@ -75,11 +96,14 @@
 
 def successful_test(name, msg, **kwargs):
     _successful_rule(
-        name = name + "_impl",
-        msg = msg,
-        out = name + "_impl.sh",
-        testonly = 1,
-        visibility = ["//visibility:private"],
+        **_helper_rule_attrs(
+            kwargs,
+            dict(
+                name = name + "_impl",
+                msg = msg,
+                out = name + "_impl.sh",
+            ),
+        )
     )
 
     _make_sh_test(name, **kwargs)
@@ -121,11 +145,14 @@
 
 def failed_test(name, msg, **kwargs):
     _failed_rule(
-        name = name + "_impl",
-        msg = msg,
-        out = name + "_impl.sh",
-        testonly = 1,
-        visibility = ["//visibility:private"],
+        **_helper_rule_attrs(
+            kwargs,
+            dict(
+                name = name + "_impl",
+                msg = msg,
+                out = name + "_impl.sh",
+            ),
+        )
     )
 
     _make_sh_test(name, **kwargs)
@@ -306,13 +333,16 @@
 
 def rule_test(name, rule, generates = None, provides = None, **kwargs):
     _rule_test_rule(
-        name = name + "_impl",
-        rule = rule,
-        generates = generates,
-        provides = provides,
-        out = name + ".sh",
-        testonly = 1,
-        visibility = ["//visibility:private"],
+        **_helper_rule_attrs(
+            kwargs,
+            dict(
+                name = name + "_impl",
+                rule = rule,
+                generates = generates,
+                provides = provides,
+                out = name + ".sh",
+            ),
+        )
     )
 
     _make_sh_test(name, **kwargs)
@@ -372,14 +402,16 @@
 
 def file_test(name, file, content = None, regexp = None, matches = None, **kwargs):
     _file_test_rule(
-        name = name + "_impl",
-        file = file,
-        content = content or "",
-        regexp = regexp or "",
-        matches = matches if (matches != None) else -1,
-        out = name + "_impl.sh",
-        testonly = 1,
-        visibility = ["//visibility:private"],
+        **_helper_rule_attrs(
+            kwargs,
+            dict(
+                name = name + "_impl",
+                file = file,
+                content = content or "",
+                regexp = regexp or "",
+                matches = matches if (matches != None) else -1,
+                out = name + "_impl.sh",
+            ),
+        )
     )
-
     _make_sh_test(name, **kwargs)