Skip to content

Commit

Permalink
Fix UUIDRequest scoping. (#10780)
Browse files Browse the repository at this point in the history
Even though the scoping test previously passed, tests in the wild
confirm A fixed scope would generate a new UUID on each ./pants
invocation against a stable pantsd. This is fixed with an arguably
simpler model that doubles down on UUIDs while we're there.
  • Loading branch information
jsirois committed Sep 15, 2020
1 parent bfd65d8 commit ed6cf39
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 14 deletions.
39 changes: 30 additions & 9 deletions src/python/pants/engine/internals/uuid.py
@@ -1,28 +1,49 @@
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import random
import uuid
from dataclasses import dataclass, field
from dataclasses import dataclass
from enum import Enum
from typing import Optional, cast

from pants.engine.rules import _uncacheable_rule, collect_rules
from pants.engine.rules import collect_rules, rule
from pants.util.meta import frozen_after_init


@dataclass(frozen=True)
class UUIDScope(Enum):
PER_CALL = "call"
PER_SESSION = "session"


@frozen_after_init
@dataclass(unsafe_hash=True)
class UUIDRequest:
randomizer: float = field(default_factory=random.random)
scope: str

def __init__(self, scope: Optional[str] = None) -> None:
self.scope = scope if scope is not None else self._to_scope_name(UUIDScope.PER_CALL)

@staticmethod
def _to_scope_name(scope: UUIDScope) -> str:
if scope == UUIDScope.PER_CALL:
return uuid.uuid4().hex
return cast(str, scope.value)

@classmethod
def scoped(cls, scope: UUIDScope) -> "UUIDRequest":
return cls(cls._to_scope_name(scope))


@_uncacheable_rule
@rule
async def generate_uuid(_: UUIDRequest) -> uuid.UUID:
"""A rule to generate a UUID.
Useful primarily to force a rule to re-run: a rule that `await Get`s on a UUIDRequest will be
uncacheable, because this rule is itself uncacheable.
Note that this will return a new UUID each time if request multiple times in a single session.
If you want two requests to return the same UUID, set the `randomizer` field in both
requests to some fixed value.
Note that this will return a new UUID each time if requested multiple times in a single session.
If you want two requests to return the same UUID, set the `scope` field in both requests to some
fixed scope value.
"""
return uuid.uuid4()

Expand Down
30 changes: 25 additions & 5 deletions src/python/pants/engine/internals/uuid_test.py
Expand Up @@ -5,7 +5,7 @@

import pytest

from pants.engine.internals.uuid import UUIDRequest
from pants.engine.internals.uuid import UUIDRequest, UUIDScope
from pants.engine.internals.uuid import rules as uuid_rules
from pants.engine.rules import QueryRule
from pants.testutil.rule_runner import RuleRunner
Expand All @@ -16,13 +16,33 @@ def rule_runner() -> RuleRunner:
return RuleRunner(rules=[*uuid_rules(), QueryRule(UUID, (UUIDRequest,))])


def test_distinct_uuids(rule_runner: RuleRunner) -> None:
def test_distinct_uuids_default_scope(rule_runner: RuleRunner) -> None:
uuid1 = rule_runner.request_product(UUID, [UUIDRequest()])
uuid2 = rule_runner.request_product(UUID, [UUIDRequest()])
assert uuid1 != uuid2


def test_identical_uuids(rule_runner: RuleRunner) -> None:
uuid1 = rule_runner.request_product(UUID, [UUIDRequest(randomizer=0.0)])
uuid2 = rule_runner.request_product(UUID, [UUIDRequest(randomizer=0.0)])
def test_distinct_uuids_different_scopes(rule_runner: RuleRunner) -> None:
uuid1 = rule_runner.request_product(UUID, [UUIDRequest(scope="this")])
uuid2 = rule_runner.request_product(UUID, [UUIDRequest(scope="that")])
assert uuid1 != uuid2


def test_identical_uuids_same_scope(rule_runner: RuleRunner) -> None:
uuid1 = rule_runner.request_product(UUID, [UUIDRequest(scope="this")])
uuid2 = rule_runner.request_product(UUID, [UUIDRequest(scope="this")])
assert uuid1 == uuid2


def test_distinct_uuids_call_scope(rule_runner: RuleRunner) -> None:
uuid1 = rule_runner.request_product(UUID, [UUIDRequest()])
uuid2 = rule_runner.request_product(UUID, [UUIDRequest(scope="bob")])
uuid3 = rule_runner.request_product(UUID, [UUIDRequest.scoped(UUIDScope.PER_CALL)])
uuid4 = rule_runner.request_product(UUID, [UUIDRequest.scoped(UUIDScope.PER_CALL)])
assert uuid1 != uuid2 != uuid3 != uuid4


def test_identical_uuids_session_scope(rule_runner: RuleRunner) -> None:
uuid1 = rule_runner.request_product(UUID, [UUIDRequest.scoped(UUIDScope.PER_SESSION)])
uuid2 = rule_runner.request_product(UUID, [UUIDRequest.scoped(UUIDScope.PER_SESSION)])
assert uuid1 == uuid2

0 comments on commit ed6cf39

Please sign in to comment.