Skip to content

Commit

Permalink
feat: Add CollectionSubject.offset
Browse files Browse the repository at this point in the history
This is a relatively thin accessor. The main advantage over directly
accessing the underlying value is the additional context information is propagated.

The user must manually pass a `factory` arg to construct the wrapped value for two reasons:
  * A collection doesn't know the types of elements it contains.
  * CollectionSubject can't depend on StrSubject because it would cause a circular
    dependency (StrSubject.split requires CollectionSubject), and finding a
    solution for this looks non-trivial.

A factory arg is necessary anyways to support custom subjects.

Also exposes StrSubject as `subjects.str` (as done with several other subjects)

Closes #41

PiperOrigin-RevId: 540009713
  • Loading branch information
rickeylev authored and Blaze Rules Copybara committed Jun 13, 2023
1 parent 6eeefb3 commit 2207e58
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 5 deletions.
33 changes: 28 additions & 5 deletions lib/private/collection_subject.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,17 @@ def _collection_subject_new(
public = struct(
# keep sorted start
actual = values,
has_size = lambda *a, **k: _collection_subject_has_size(self, *a, **k),
contains = lambda *a, **k: _collection_subject_contains(self, *a, **k),
contains_at_least = lambda *a, **k: _collection_subject_contains_at_least(self, *a, **k),
contains_at_least_predicates = lambda *a, **k: _collection_subject_contains_at_least_predicates(self, *a, **k),
contains_exactly = lambda *a, **k: _collection_subject_contains_exactly(self, *a, **k),
contains_exactly_predicates = lambda *a, **k: _collection_subject_contains_exactly_predicates(self, *a, **k),
contains_none_of = lambda *a, **k: _collection_subject_contains_none_of(self, *a, **k),
contains_predicate = lambda *a, **k: _collection_subject_contains_predicate(self, *a, **k),
has_size = lambda *a, **k: _collection_subject_has_size(self, *a, **k),
not_contains = lambda *a, **k: _collection_subject_not_contains(self, *a, **k),
not_contains_predicate = lambda *a, **k: _collection_subject_not_contains_predicate(self, *a, **k),
offset = lambda *a, **k: _collection_subject_offset(self, *a, **k),
# keep sorted end
)
self = struct(
Expand Down Expand Up @@ -334,17 +335,39 @@ def _collection_subject_not_contains_predicate(self, matcher):
sort = self.sortable,
)

def _collection_subject_offset(self, offset, factory):
"""Fetches an element from the collection as a subject.
Args:
self: implicitly added.
offset: ([`int`]) the offset to fetch
factory: ([`callable`]). The factory function to use to create
the subject for the offset's value. It must have the following
signature: `def factory(value, *, meta)`.
Returns:
Object created by `factory`.
"""
value = self.actual[offset]
return factory(
value,
meta = self.meta.derive("offset({})".format(offset)),
)

# We use this name so it shows up nice in docs.
# buildifier: disable=name-conventions
CollectionSubject = struct(
new = _collection_subject_new,
has_size = _collection_subject_has_size,
# keep sorted start
contains = _collection_subject_contains,
contains_at_least = _collection_subject_contains_at_least,
contains_at_least_predicates = _collection_subject_contains_at_least_predicates,
contains_exactly = _collection_subject_contains_exactly,
contains_exactly_predicates = _collection_subject_contains_exactly_predicates,
contains_none_of = _collection_subject_contains_none_of,
contains_predicate = _collection_subject_contains_predicate,
contains_at_least = _collection_subject_contains_at_least,
contains_at_least_predicates = _collection_subject_contains_at_least_predicates,
has_size = _collection_subject_has_size,
new = _collection_subject_new,
not_contains_predicate = _collection_subject_not_contains_predicate,
offset = _collection_subject_offset,
# keep sorted end
)
2 changes: 2 additions & 0 deletions lib/truth.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ load("//lib/private:depset_file_subject.bzl", "DepsetFileSubject")
load("//lib/private:expect.bzl", "Expect")
load("//lib/private:int_subject.bzl", "IntSubject")
load("//lib/private:label_subject.bzl", "LabelSubject")
load("//lib/private:str_subject.bzl", "StrSubject")
load("//lib/private:matching.bzl", _matching = "matching")

# Rather than load many symbols, just load this symbol, and then all the
Expand All @@ -66,5 +67,6 @@ subjects = struct(
depset_file = DepsetFileSubject.new,
int = IntSubject.new,
label = LabelSubject.new,
str = StrSubject.new,
# keep sorted end
)
26 changes: 26 additions & 0 deletions tests/truth_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,32 @@ def _collection_not_contains_predicate_test(env, _target):

_suite.append(collection_not_contains_predicate_test)

def collection_offset_test(name):
analysis_test(name, impl = _collection_offset_test, target = "truth_tests_helper")

def _collection_offset_test(env, _target):
fake_env = _fake_env(env)
subject = truth.expect(fake_env).that_collection(["a", "b", "c"])

offset_value = subject.offset(0, factory = lambda v, meta: v)
ut_asserts.true(env, offset_value == "a", "unexpected offset value at 0")

offset_value = subject.offset(-1, factory = lambda v, meta: v)
ut_asserts.true(env, offset_value == "c", "unexpected offset value at -1")

subject.offset(1, factory = subjects.str).equals("not-b")

_assert_failure(
fake_env,
[".offset(1)"],
env = env,
msg = "offset error message context not found",
)

_end(env, fake_env)

_suite.append(collection_offset_test)

def execution_info_test(name):
analysis_test(name, impl = _execution_info_test, target = "truth_tests_helper")

Expand Down

0 comments on commit 2207e58

Please sign in to comment.