diff --git a/pants-plugins/api_spec/rules.py b/pants-plugins/api_spec/rules.py index adf0737db81..29104abbe51 100644 --- a/pants-plugins/api_spec/rules.py +++ b/pants-plugins/api_spec/rules.py @@ -45,8 +45,12 @@ from api_spec.target_types import APISpecSourceField -GENERATE_SCRIPT = "generate_api_spec" -VALIDATE_SCRIPT = "validate_api_spec" +# these constants are also used in the tests +CMD_SOURCE_ROOT = "st2common" +CMD_DIR = "st2common/st2common/cmd" +CMD_MODULE = "st2common.cmd" +GENERATE_CMD = "generate_api_spec" +VALIDATE_CMD = "validate_api_spec" SPEC_HEADER = b"""\ # NOTE: This file is auto-generated - DO NOT EDIT MANUALLY @@ -65,12 +69,12 @@ class APISpecFieldSet(FieldSet): class GenerateAPISpecViaFmtTargetsRequest(FmtTargetsRequest): field_set_type = APISpecFieldSet - name = GENERATE_SCRIPT + name = GENERATE_CMD class ValidateAPISpecRequest(LintTargetsRequest): field_set_type = APISpecFieldSet - name = VALIDATE_SCRIPT + name = VALIDATE_CMD @rule( @@ -115,14 +119,14 @@ async def generate_api_spec_via_fmt( PexFromTargetsRequest( [ Address( - "st2common/st2common/cmd", + CMD_DIR, target_name="cmd", - relative_file_path=f"{GENERATE_SCRIPT}.py", + relative_file_path=f"{GENERATE_CMD}.py", ), ], - output_filename=f"{GENERATE_SCRIPT}.pex", + output_filename=f"{GENERATE_CMD}.pex", internal_only=True, - main=EntryPoint.parse(f"st2common.cmd.{GENERATE_SCRIPT}:main"), + main=EntryPoint.parse(f"{CMD_MODULE}.{GENERATE_CMD}:main"), ), ) @@ -165,6 +169,7 @@ async def generate_api_spec_via_fmt( output_digest = await Get(Digest, CreateDigest(contents)) output_snapshot = await Get(Snapshot, Digest, output_digest) + # TODO: Drop result.stdout since we already wrote it to a file? return FmtResult.create(request, result, output_snapshot, strip_chroot_path=True) @@ -210,14 +215,14 @@ async def validate_api_spec( PexFromTargetsRequest( [ Address( - "st2common/st2common/cmd", + CMD_DIR, target_name="cmd", - relative_file_path=f"{VALIDATE_SCRIPT}.py", + relative_file_path=f"{VALIDATE_CMD}.py", ), ], - output_filename=f"{VALIDATE_SCRIPT}.pex", + output_filename=f"{VALIDATE_CMD}.pex", internal_only=True, - main=EntryPoint.parse(f"st2common.cmd.{VALIDATE_SCRIPT}:main"), + main=EntryPoint.parse(f"{CMD_MODULE}.{VALIDATE_CMD}:main"), ), ) diff --git a/pants-plugins/api_spec/rules_test.py b/pants-plugins/api_spec/rules_test.py new file mode 100644 index 00000000000..944b5ed0a18 --- /dev/null +++ b/pants-plugins/api_spec/rules_test.py @@ -0,0 +1,162 @@ +# Copyright 2022 The StackStorm Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +import os + +import pytest + +from pants.backend.python import target_types_rules +from pants.backend.python.target_types import PythonSourcesGeneratorTarget + +from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest +from pants.engine.addresses import Address +from pants.engine.fs import CreateDigest, Digest, FileContent, Snapshot +from pants.engine.target import Target +from pants.core.goals.fmt import FmtResult +from pants.testutil.rule_runner import QueryRule, RuleRunner + +from .rules import ( + CMD_DIR, + CMD_SOURCE_ROOT, + GENERATE_CMD, + APISpecFieldSet, + GenerateAPISpecViaFmtTargetsRequest, + rules as api_spec_rules, +) +from .target_types import APISpec + + +@pytest.fixture +def rule_runner() -> RuleRunner: + return RuleRunner( + rules=[ + *api_spec_rules(), + *target_types_rules.rules(), + QueryRule(FmtResult, (GenerateAPISpecViaFmtTargetsRequest,)), + QueryRule(SourceFiles, (SourceFilesRequest,)), + ], + target_types=[APISpec, PythonSourcesGeneratorTarget], + ) + + +def run_st2_generate_api_spec( + rule_runner: RuleRunner, + targets: list[Target], + *, + extra_args: list[str] | None = None, +) -> FmtResult: + rule_runner.set_options( + [ + "--backend-packages=api_spec", + f"--source-root-patterns=/{CMD_SOURCE_ROOT}", + *(extra_args or ()), + ], + env_inherit={"PATH", "PYENV_ROOT", "HOME"}, + ) + field_sets = [APISpecFieldSet.create(tgt) for tgt in targets] + input_sources = rule_runner.request( + SourceFiles, + [ + SourceFilesRequest(field_set.sources for field_set in field_sets), + ], + ) + fmt_result = rule_runner.request( + FmtResult, + [ + GenerateAPISpecViaFmtTargetsRequest( + field_sets, snapshot=input_sources.snapshot + ), + ], + ) + return fmt_result + + +# copied from pantsbuild/pants.git/src/python/pants/backend/python/lint/black/rules_integration_test.py +def get_snapshot(rule_runner: RuleRunner, source_files: dict[str, str]) -> Snapshot: + files = [ + FileContent(path, content.encode()) for path, content in source_files.items() + ] + digest = rule_runner.request(Digest, [CreateDigest(files)]) + return rule_runner.request(Snapshot, [digest]) + + +# add dummy script at st2common/st2common/cmd/generate_api_spec.py that the test can load. +GENERATE_API_SPEC_PY = """ +import os + + +def main(): + api_spec_text = "{api_spec_text}" + print(api_spec_text) +""" + + +def write_generate_files( + api_spec_dir: str, api_spec_file: str, before: str, after: str, rule_runner: RuleRunner +) -> None: + files = { + f"{api_spec_dir}/{api_spec_file}": before, + f"{api_spec_dir}/BUILD": "api_spec(name='t')", + # add in the target that's hard-coded in the generate_api_spec_via_fmt rue + f"{CMD_DIR}/{GENERATE_CMD}.py": GENERATE_API_SPEC_PY.format( + api_spec_dir=api_spec_dir, api_spec_text=after + ), + f"{CMD_DIR}/BUILD": "python_sources()", + } + + module = CMD_DIR + while module != CMD_SOURCE_ROOT: + files[f"{module}/__init__.py"] = "" + module = os.path.dirname(module) + + rule_runner.write_files(files) + + +def test_generate_changed(rule_runner: RuleRunner) -> None: + write_generate_files( + api_spec_dir="my_dir", + api_spec_file="dummy.yaml", + before="BEFORE", + after="AFTER", + rule_runner=rule_runner, + ) + + tgt = rule_runner.get_target( + Address("my_dir", target_name="t", relative_file_path="dummy.yaml") + ) + fmt_result = run_st2_generate_api_spec(rule_runner, [tgt]) + assert fmt_result.output == get_snapshot( + rule_runner, {"my_dir/dummy.yaml": "AFTER"} + ) + assert fmt_result.did_change is True + + +def test_generate_unchanged(rule_runner: RuleRunner) -> None: + write_generate_files( + api_spec_dir="my_dir", + api_spec_file="dummy.yaml", + before="AFTER", + after="AFTER", + rule_runner=rule_runner, + ) + + tgt = rule_runner.get_target( + Address("my_dir", target_name="t", relative_file_path="dummy.yaml") + ) + fmt_result = run_st2_generate_api_spec(rule_runner, [tgt]) + assert fmt_result.output == get_snapshot( + rule_runner, {"my_dir/dummy.yaml": "AFTER"} + ) + assert fmt_result.did_change is False