Repository: pantsbuild/pants

Issue:
Add test timeouts to `helm_unittest_test` targets
**Is your feature request related to a problem? Please describe.**
Having the ability of configuring test timeouts in `helm_unittest_test` and `helm_unittest_tests` targets using a `timeout` field.

**Describe the solution you'd like**
The solution should be based on the refactoring done at #16126, which brings a more general way of configuring global settings for test timeouts (via the `test` goal options) and also provides with a base field class that can be used to define the `timeout` field in the test targets.

The solution therefore would have the posibility of enabling/disabling test timeouts, setting a max timeout, a default one (all overridable at the cli) plus the customisation at the target level or the `__defaults__` construct.

**Describe alternatives you've considered**
N/A

**Additional context**
N/A



Discussion:

Resolving patch:
diff --git a/src/python/pants/backend/helm/target_types.py b/src/python/pants/backend/helm/target_types.py
--- a/src/python/pants/backend/helm/target_types.py
+++ b/src/python/pants/backend/helm/target_types.py
@@ -7,6 +7,7 @@
 
 from pants.backend.helm.resolve.remotes import ALL_DEFAULT_HELM_REGISTRIES
 from pants.core.goals.package import OutputPathField
+from pants.core.goals.test import TestTimeoutField
 from pants.engine.rules import collect_rules, rule
 from pants.engine.target import (
     COMMON_TARGET_FIELDS,
@@ -213,6 +214,10 @@ class HelmUnitTestDependenciesField(Dependencies):
     pass
 
 
+class HelmUnitTestTimeoutField(TestTimeoutField):
+    help = "A timeout (in seconds) used by each Helm Unittest test file belonging to this target."
+
+
 class HelmUnitTestSourceField(SingleSourceField):
     expected_file_extensions = (
         ".yaml",
@@ -232,6 +237,7 @@ class HelmUnitTestTestTarget(Target):
         HelmUnitTestSourceField,
         HelmUnitTestDependenciesField,
         HelmUnitTestStrictField,
+        HelmUnitTestTimeoutField,
     )
     help = "A single helm-unittest suite file."
 
@@ -270,9 +276,10 @@ class HelmUnitTestTestsGeneratorTarget(TargetFilesGenerator):
         HelmUnitTestGeneratingSourcesField,
         HelmUnitTestDependenciesField,
         HelmUnitTestStrictField,
+        HelmUnitTestTimeoutField,
     )
     generated_target_cls = HelmUnitTestTestTarget
-    copied_fields = COMMON_TARGET_FIELDS
+    copied_fields = (*COMMON_TARGET_FIELDS, HelmUnitTestTimeoutField, HelmUnitTestStrictField)
     moved_fields = (HelmUnitTestDependenciesField,)
     help = f"Generates a `{HelmUnitTestTestTarget.alias}` target per each file in the `{HelmUnitTestGeneratingSourcesField.alias}` field."
 
diff --git a/src/python/pants/backend/helm/util_rules/tool.py b/src/python/pants/backend/helm/util_rules/tool.py
--- a/src/python/pants/backend/helm/util_rules/tool.py
+++ b/src/python/pants/backend/helm/util_rules/tool.py
@@ -71,6 +71,7 @@ class HelmProcess:
     extra_env: FrozenDict[str, str]
     extra_immutable_input_digests: FrozenDict[str, Digest]
     cache_scope: ProcessCacheScope | None
+    timeout_seconds: int | None
     output_directories: tuple[str, ...]
     output_files: tuple[str, ...]
 
@@ -86,6 +87,7 @@ def __init__(
         extra_env: Mapping[str, str] | None = None,
         extra_immutable_input_digests: Mapping[str, Digest] | None = None,
         cache_scope: ProcessCacheScope | None = None,
+        timeout_seconds: int | None = None,
     ):
         self.argv = tuple(argv)
         self.input_digest = input_digest
@@ -96,6 +98,7 @@ def __init__(
         self.extra_env = FrozenDict(extra_env or {})
         self.extra_immutable_input_digests = FrozenDict(extra_immutable_input_digests or {})
         self.cache_scope = cache_scope
+        self.timeout_seconds = timeout_seconds
 
 
 @rule(desc="Download and configure Helm", level=LogLevel.DEBUG)
@@ -191,6 +194,7 @@ def helm_process(request: HelmProcess, helm_binary: HelmBinary) -> Process:
         output_directories=request.output_directories,
         output_files=request.output_files,
         cache_scope=request.cache_scope or ProcessCacheScope.SUCCESSFUL,
+        timeout_seconds=request.timeout_seconds,
     )
 
 


Test patch:
diff --git a/src/python/pants/backend/helm/test/unittest.py b/src/python/pants/backend/helm/test/unittest.py
--- a/src/python/pants/backend/helm/test/unittest.py
+++ b/src/python/pants/backend/helm/test/unittest.py
@@ -16,6 +16,7 @@
     HelmUnitTestStrictField,
     HelmUnitTestTestsGeneratorTarget,
     HelmUnitTestTestTarget,
+    HelmUnitTestTimeoutField,
 )
 from pants.backend.helm.util_rules.chart import HelmChart, HelmChartRequest
 from pants.backend.helm.util_rules.tool import HelmProcess
@@ -64,6 +65,7 @@ class HelmUnitTestFieldSet(TestFieldSet):
     source: HelmUnitTestSourceField
     dependencies: HelmUnitTestDependenciesField
     strict: HelmUnitTestStrictField
+    timeout: HelmUnitTestTimeoutField
 
 
 @rule(desc="Run Helm Unittest", level=LogLevel.DEBUG)
@@ -135,6 +137,7 @@ async def run_helm_unittest(
             description=f"Running Helm unittest suite {field_set.address}",
             input_digest=input_digest,
             cache_scope=cache_scope,
+            timeout_seconds=field_set.timeout.calculate_from_global_options(test_subsystem),
             output_directories=(reports_dir,),
         ),
     )
diff --git a/src/python/pants/backend/helm/util_rules/tool_test.py b/src/python/pants/backend/helm/util_rules/tool_test.py
--- a/src/python/pants/backend/helm/util_rules/tool_test.py
+++ b/src/python/pants/backend/helm/util_rules/tool_test.py
@@ -55,6 +55,7 @@ def test_create_helm_process(rule_runner: RuleRunner) -> None:
         extra_env={"FOO_ENV": "1"},
         output_directories=["foo_out"],
         cache_scope=ProcessCacheScope.ALWAYS,
+        timeout_seconds=30,
     )
     process = rule_runner.request(Process, [helm_process])
 
@@ -68,3 +69,4 @@ def test_create_helm_process(rule_runner: RuleRunner) -> None:
     assert process.env == FrozenDict({**helm_binary.env, **helm_process.extra_env})
     assert process.output_directories == helm_process.output_directories
     assert process.cache_scope == helm_process.cache_scope
+    assert process.timeout_seconds == helm_process.timeout_seconds