Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests to GraphQL queries #15792

Merged
merged 6 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 146 additions & 110 deletions 3rdparty/python/pytest.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ args = ["-i 2", "-ci", "-sr"]
args = ["--no-header"]
extra_requirements.add = [
"ipdb",
"pytest-asyncio",
"pytest-html",
"pytest-icdiff",
"pygments",
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,6 @@ module = [
"setproctitle",
]
ignore_missing_imports = true

[tool.pytest.ini_options]
asyncio_mode = "auto"
Eric-Arellano marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 4 additions & 1 deletion src/python/pants/backend/explorer/graphql/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ def create_request_context(self) -> dict[str, RequestState]:

@staticmethod
def request_state_from_info(info: Info) -> RequestState:
return cast(RequestState, info.context["pants_request_state"])
assert info.context is not None
request_state = cast("RequestState | None", info.context.get("pants_request_state"))
assert request_state is not None
return request_state
8 changes: 8 additions & 0 deletions src/python/pants/backend/explorer/graphql/query/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@
# Licensed under the Apache License, Version 2.0 (see LICENSE).

python_sources()

python_tests(
name="tests",
)

python_test_utils(
name="test_utils",
)
77 changes: 77 additions & 0 deletions src/python/pants/backend/explorer/graphql/query/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from textwrap import dedent

import pytest

from pants.backend.docker.target_types import DockerImageTarget
from pants.backend.explorer.graphql.rules import rules
from pants.backend.explorer.graphql.setup import create_schema
from pants.backend.explorer.rules import validate_explorer_dependencies
from pants.backend.project_info import peek
from pants.engine.explorer import RequestState
from pants.engine.target import RegisteredTargetTypes
from pants.help.help_info_extracter import AllHelpInfo, HelpInfoExtracter
from pants.testutil.rule_runner import RuleRunner


@pytest.fixture(scope="session")
def schema():
return create_schema()


@pytest.fixture
def context(all_help_info: AllHelpInfo, rule_runner: RuleRunner) -> dict:
return dict(
pants_request_state=RequestState(
all_help_info, rule_runner.build_config, rule_runner.scheduler
)
)


@pytest.fixture
def all_help_info(rule_runner: RuleRunner) -> AllHelpInfo:
def fake_consumed_scopes_mapper(scope: str) -> tuple[str, ...]:
return ("somescope", f"used_by_{scope or 'GLOBAL_SCOPE'}")

return HelpInfoExtracter.get_all_help_info(
options=rule_runner.options_bootstrapper.full_options(rule_runner.build_config),
union_membership=rule_runner.union_membership,
consumed_scopes_mapper=fake_consumed_scopes_mapper,
registered_target_types=RegisteredTargetTypes.create(rule_runner.build_config.target_types),
build_configuration=rule_runner.build_config,
)


@pytest.fixture
def rule_runner() -> RuleRunner:
return RuleRunner(
rules=(
validate_explorer_dependencies,
*peek.rules(),
*rules(),
),
target_types=(DockerImageTarget,),
)


@pytest.fixture(scope="session")
def queries() -> str:
return dedent(
"""\
query TestRulesQuery($name: String, $limit: Int) {
rules(query:{nameRe: $name, limit: $limit}) { name }
}

query TestTargetsQuery($specs: [String!], $targetType: String, $limit: Int) {
targets(query:{specs: $specs, targetType: $targetType, limit: $limit}) { targetType }
}

query TestTargetTypesQuery($alias: String, $limit: Int) {
targetTypes(query:{aliasRe: $alias, limit: $limit}) { alias }
}
"""
)
6 changes: 3 additions & 3 deletions src/python/pants/backend/explorer/graphql/query/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ def filter(query: RulesQuery | None, rules: Iterable[RuleInfo]) -> Iterator[Rule
name_pattern = query.name_re and re.compile(query.name_re)
count = 0
for info in rules:
if name_pattern and not re.match(name_pattern, info.name):
if name_pattern and not re.search(name_pattern, info.name):
continue
if query.limit is not None and count >= query.limit:
return
kaos marked this conversation as resolved.
Show resolved Hide resolved
yield info
count += 1
if query.limit and count >= query.limit:
return


@strawberry.type
Expand Down
50 changes: 50 additions & 0 deletions src/python/pants/backend/explorer/graphql/query/rules_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

import pytest


@pytest.mark.parametrize(
"variables, expected_data",
[
(
{"name": r"\.explorer\."},
{
"rules": [
{"name": "pants.backend.explorer.rules.validate_explorer_dependencies"},
{"name": "pants.backend.explorer.graphql.rules.get_graphql_uvicorn_setup"},
]
},
),
(
{"name": r"\.graphql\."},
{
"rules": [
{"name": "pants.backend.explorer.graphql.rules.get_graphql_uvicorn_setup"},
]
},
),
(
{"limit": 1},
{
"rules": [
{"name": "pants.backend.explorer.rules.validate_explorer_dependencies"},
]
},
),
(
{"limit": 0},
{"rules": []},
),
],
)
def test_rules_query(
schema, queries: str, variables: dict, expected_data: dict, context: dict
) -> None:
actual_result = schema.execute_sync(
queries, variable_values=variables, context_value=context, operation_name="TestRulesQuery"
)
assert actual_result.errors is None
assert actual_result.data == expected_data
8 changes: 4 additions & 4 deletions src/python/pants/backend/explorer/graphql/query/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ def filter(
for info in target_types:
if alias_pattern and not re.match(alias_pattern, info.alias):
continue
if query.limit is not None and count >= query.limit:
return
yield info
count += 1
if query.limit and count >= query.limit:
return


@strawberry.input(description="Filter targets based on the supplied query.")
Expand Down Expand Up @@ -136,10 +136,10 @@ def filter(query: TargetsQuery | None, targets: Iterable[TargetData]) -> Iterato
for data in targets:
if query.target_type and data.target.alias != query.target_type:
continue
if query.limit is not None and count >= query.limit:
return
kaos marked this conversation as resolved.
Show resolved Hide resolved
yield data
count += 1
if query.limit and count >= query.limit:
return


@strawberry.type(description="Get targets related info.")
Expand Down
81 changes: 81 additions & 0 deletions src/python/pants/backend/explorer/graphql/query/targets_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

import pytest

from pants.testutil.rule_runner import RuleRunner


@pytest.mark.parametrize(
"variables, expected_data",
[
(
{},
{"targets": [{"targetType": "docker_image"}]},
),
(
{"limit": 0},
{"targets": []},
),
(
{"specs": ["src/test:test"]},
{"targets": [{"targetType": "docker_image"}]},
),
],
)
async def test_targets_query(
rule_runner: RuleRunner,
schema,
queries: str,
variables: dict,
expected_data: dict,
context: dict,
) -> None:
rule_runner.write_files(
{
"src/test/BUILD": "docker_image()",
"src/test/Dockerfile": "",
}
)
actual_result = await schema.execute(
queries, variable_values=variables, context_value=context, operation_name="TestTargetsQuery"
)
assert actual_result.errors is None
assert actual_result.data == expected_data


@pytest.mark.parametrize(
"variables, expected_data",
[
(
{},
{
"targetTypes": [
{"alias": "docker_image"},
]
},
),
(
{"limit": 0},
{"targetTypes": []},
),
],
)
async def test_target_types_query(
rule_runner: RuleRunner,
schema,
queries: str,
variables: dict,
expected_data: dict,
context: dict,
) -> None:
actual_result = await schema.execute(
queries,
variable_values=variables,
context_value=context,
operation_name="TestTargetTypesQuery",
)
assert actual_result.errors is None
assert actual_result.data == expected_data
13 changes: 8 additions & 5 deletions src/python/pants/backend/explorer/graphql/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,21 @@ def render(self, content) -> bytes:
).encode("utf-8")


def create_schema() -> strawberry.Schema:
# Monkey patch, due to limitations in configurability.
strawberry.fastapi.router.JSONResponse = ExplorerJSONResponse # type: ignore[attr-defined]

return strawberry.Schema(query=Query)


def graphql_uvicorn_setup(
browser: Browser,
graphql: GraphQLSubsystem,
route: str = "/graphql",
) -> Callable[[UvicornServer], None]:
def setup(uvicorn: UvicornServer) -> None:
# Monkey patch, due to limitations in configurability.
strawberry.fastapi.router.JSONResponse = ExplorerJSONResponse # type: ignore[attr-defined]

schema = strawberry.Schema(query=Query)
graphql_app = GraphQLRouter(
schema, context_getter=GraphQLContext(uvicorn).create_request_context
create_schema(), context_getter=GraphQLContext(uvicorn).create_request_context
)

uvicorn.app.include_router(graphql_app, prefix=route)
Expand Down