e69745adc8
Fixed #99 : SheerkaQueryManager: I can manage contains predicate when filtering objects Fixed #97 : ERROR: list indices must be integers or slices, not Concept Fixed #96 : SequenceNodeParser: SequenceNodeParser must correctly handle concept definition Fixed #95 : ResolveAmbiguity must not remove concepts that do not require evaluation Fixed #94 : Concepts with the same key are lost when new ontology Fixed #93 : Introduce BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED Fixed #92 : ExpressionParser: Implement compile_disjunctions() Fixed #91 : Implement get_concepts_complexity(context, concepts, concept_parts) Fixed #90 : ResolveAmbiguity : where predicate is not used to resolve ambiguity Fixed #89 : ResolveAmbiguityEvaluator: Concepts embedded in ConceptNode are not resolved Fixed #88: SyaNodeParser: Parse multiple parameters when some of the are not recognized Fixed #87: SyaNodeParser : Parse the multiple parameters
510 lines
20 KiB
Python
510 lines
20 KiB
Python
from core.builtin_concepts import BuiltinConcepts
|
|
from core.concept import Concept
|
|
from core.sheerka.services.SheerkaIsAManager import SheerkaIsAManager
|
|
|
|
from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka
|
|
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
|
|
|
|
|
class TestSheerkaIsAManager(TestUsingMemoryBasedSheerka):
|
|
|
|
def test_i_can_add_a_concept_to_a_set(self):
|
|
sheerka, context, foo, group = self.init_concepts(
|
|
Concept("foo"),
|
|
Concept("group"),
|
|
cache_only=False
|
|
)
|
|
|
|
res = sheerka.add_concept_to_set(context, foo, group)
|
|
|
|
assert res.status
|
|
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
|
|
|
|
group_elements = sheerka.om.get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY, group.id)
|
|
assert group_elements == {foo.id}
|
|
|
|
# it can be persisted
|
|
sheerka.om.commit(context)
|
|
assert sheerka.om.current_sdp().get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY, group.id) == {foo.id}
|
|
|
|
def test_i_cannot_add_the_same_concept_twice_in_a_set(self):
|
|
sheerka, context, foo, group = self.init_concepts(Concept("foo"), Concept("group"))
|
|
sheerka.add_concept_to_set(context, foo, group)
|
|
|
|
# add again
|
|
res = sheerka.add_concept_to_set(context, foo, group)
|
|
|
|
assert not res.status
|
|
assert sheerka.isinstance(res.body, BuiltinConcepts.CONCEPT_ALREADY_IN_SET)
|
|
assert res.body.body == foo
|
|
assert res.body.concept_set == group
|
|
|
|
all_entries = sheerka.om.get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY, group.id)
|
|
assert all_entries == {foo.id}
|
|
|
|
def test_i_can_have_multiple_groups(self):
|
|
sheerka, context, foo, bar, baz, group1, group2 = self.init_concepts(
|
|
Concept("foo"),
|
|
Concept("bar"),
|
|
Concept("baz"),
|
|
Concept("group1"),
|
|
Concept("group2"),
|
|
cache_only=False
|
|
)
|
|
|
|
assert sheerka.add_concept_to_set(context, foo, group1).status
|
|
assert sheerka.add_concept_to_set(context, bar, group1).status
|
|
assert sheerka.add_concept_to_set(context, bar, group2).status
|
|
assert sheerka.add_concept_to_set(context, baz, group2).status
|
|
|
|
assert sheerka.om.get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY, group1.id) == {foo.id, bar.id}
|
|
assert sheerka.om.get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY, group2.id) == {baz.id, bar.id}
|
|
|
|
# I can save in db
|
|
sheerka.om.commit(context)
|
|
assert sheerka.om.current_sdp().get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY) == {
|
|
'1004': {'1001', '1002'}, '1005': {'1002', '1003'}
|
|
}
|
|
|
|
def test_i_get_elements_from_a_set(self):
|
|
sheerka, context, one, two, three, number = self.init_concepts(
|
|
Concept("one"), Concept("two"), Concept("three"), Concept("number"))
|
|
|
|
for c in [one, two, three]:
|
|
sheerka.add_concept_to_set(context, c, number)
|
|
|
|
elements = sheerka.get_set_elements(context, number)
|
|
assert set(elements) == {one, two, three}
|
|
|
|
def test_i_cannot_get_elements_if_not_a_set(self):
|
|
sheerka, context, one = self.init_concepts(Concept("one"))
|
|
|
|
error = sheerka.get_set_elements(context, one)
|
|
|
|
assert sheerka.isinstance(error, BuiltinConcepts.NOT_A_SET)
|
|
assert error.body == one
|
|
|
|
def test_isa(self):
|
|
sheerka, context, blue, color = self.init_concepts(Concept("blue"), Concept("color"))
|
|
assert not sheerka.isa(blue, color)
|
|
|
|
blue_instance = sheerka.new("blue")
|
|
assert not sheerka.isa(blue_instance, color)
|
|
|
|
sheerka.set_isa(context, blue_instance, color)
|
|
assert sheerka.isa(blue_instance, color)
|
|
|
|
# isa tests the id of a concept, not it's content
|
|
another_color_instance_but_with_a_body = sheerka.new(color, body="a body")
|
|
assert sheerka.isa(blue_instance, another_color_instance_but_with_a_body)
|
|
|
|
# isa, when EVAL_GLOBAL_TRUTH_REQUESTED is not activated, only affect the current concept
|
|
another_blue_instance = sheerka.new("blue")
|
|
assert not sheerka.isa(another_blue_instance, color)
|
|
|
|
# when EVAL_GLOBAL_TRUTH_REQUESTED is not activated, color is not a set
|
|
assert not sheerka.isinset(blue_instance, color)
|
|
assert not sheerka.isaset(context, color)
|
|
|
|
def test_isa_global_truth(self):
|
|
sheerka, context, blue, color = self.init_concepts(Concept("blue"), Concept("color"))
|
|
|
|
blue_instance = sheerka.new("blue")
|
|
assert not sheerka.isa(blue_instance, color)
|
|
assert not sheerka.isaset(context, color)
|
|
assert not sheerka.isinset(blue_instance, color)
|
|
|
|
context.add_to_private_hints(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED)
|
|
|
|
sheerka.set_isa(context, blue_instance, color)
|
|
assert sheerka.isa(blue_instance, color)
|
|
assert sheerka.isaset(context, color)
|
|
assert sheerka.isinset(blue_instance, color)
|
|
|
|
# all blue instances are now a color
|
|
another_blue_instance = sheerka.new("blue")
|
|
assert sheerka.isa(another_blue_instance, color)
|
|
assert sheerka.isaset(context, color)
|
|
assert sheerka.isinset(another_blue_instance, color)
|
|
|
|
def test_isaset(self):
|
|
sheerka, context, group, foo = self.init_concepts(Concept("group"), Concept("foo"))
|
|
|
|
assert not sheerka.isaset(context, group)
|
|
assert not sheerka.isinset(foo, group)
|
|
|
|
context = self.get_context(sheerka) # another context ?
|
|
sheerka.add_concept_to_set(context, foo, group)
|
|
|
|
assert sheerka.isaset(context, group)
|
|
assert sheerka.isinset(foo, group)
|
|
|
|
def test_i_can_define_a_group_from_another_group(self):
|
|
sheerka, context, foo, bar, group1, group2 = self.init_concepts(
|
|
"foo", "bar", "group1", Concept("group2", body="group1"))
|
|
|
|
service = sheerka.services[SheerkaIsAManager.NAME]
|
|
service.add_concepts_to_set(context, [foo, bar], group1)
|
|
|
|
assert sheerka.isaset(context, group2)
|
|
assert set(sheerka.get_set_elements(context, group2)) == {foo, bar}
|
|
|
|
def test_i_can_define_subset_of_another_group(self):
|
|
sheerka, context, one, two, three, four, five, number, sub_number = self.init_concepts(
|
|
Concept("one", body="1"),
|
|
Concept("two", body="2"),
|
|
Concept("three", body="3"),
|
|
Concept("four", body="4"),
|
|
Concept("five", body="5"),
|
|
Concept("number"),
|
|
Concept("sub_number", body="number", where="number < 4")
|
|
)
|
|
service = sheerka.services[SheerkaIsAManager.NAME]
|
|
service.add_concepts_to_set(context, [one, two, three, four, five], number)
|
|
|
|
assert sheerka.isaset(context, sub_number)
|
|
# compare ids, as concepts are evaluated in get_set_elements
|
|
actual_ids = set(c.id for c in sheerka.get_set_elements(context, sub_number))
|
|
expected_ids = set(c.id for c in [one, two, three])
|
|
assert actual_ids == expected_ids
|
|
|
|
def test_i_can_define_subset_of_subset(self):
|
|
sheerka, context, one, two, three, four, five, number, sub_number, sub_sub_number = self.init_concepts(
|
|
Concept("one", body="1"),
|
|
Concept("two", body="2"),
|
|
Concept("three", body="3"),
|
|
Concept("four", body="4"),
|
|
Concept("five", body="5"),
|
|
Concept("number"),
|
|
Concept("sub_number", body="number", where="number < 4"),
|
|
Concept("sub_sub_number", body="sub_number", where="sub_number > 2")
|
|
)
|
|
service = sheerka.services[SheerkaIsAManager.NAME]
|
|
service.add_concepts_to_set(context, [one, two, three, four, five], number)
|
|
|
|
assert sheerka.isaset(context, sub_sub_number)
|
|
|
|
# compare ids, as concepts are evaluated in get_set_elements
|
|
actual_ids = set(c.id for c in sheerka.get_set_elements(context, sub_sub_number))
|
|
expected_ids = set(c.id for c in [three])
|
|
assert actual_ids == expected_ids
|
|
|
|
def test_i_can_define_subset_of_another_set_when_some_concept_do_not_have_a_defined_body(self):
|
|
"""
|
|
Example
|
|
def concept unit from number where number <10
|
|
It implies that all elements in number have a (numeric) body
|
|
What if its not the case ?
|
|
:return:
|
|
"""
|
|
|
|
sheerka, context, one, two, three, four, five, number, sub_number = self.init_concepts(
|
|
Concept("one", body="1"),
|
|
Concept("two"),
|
|
Concept("three", body="3"),
|
|
Concept("four"),
|
|
Concept("five", body="5"),
|
|
Concept("number"),
|
|
Concept("sub_number", body="number", where="number < 4")
|
|
)
|
|
service = sheerka.services[SheerkaIsAManager.NAME]
|
|
service.add_concepts_to_set(context, [one, two, three, four, five], number)
|
|
|
|
assert sheerka.isaset(context, sub_number)
|
|
# compare ids, as concepts are evaluated in get_set_elements
|
|
actual_ids = set(c.id for c in sheerka.get_set_elements(context, sub_number))
|
|
expected_ids = set(c.id for c in [one, three])
|
|
assert actual_ids == expected_ids
|
|
|
|
def test_i_can_define_subset_of_another_set_when_some_concept_are_bnf(self):
|
|
"""
|
|
Other case:
|
|
twenties isa number, but its body is a source code, not a constant numeric number
|
|
is it included ?
|
|
:return:
|
|
"""
|
|
|
|
sheerka, context, one, two, twenty, twenties, number, sub_number = self.init_concepts(
|
|
Concept("one", body="1"),
|
|
Concept("two", body="2"),
|
|
Concept("twenty", body="20"),
|
|
Concept("twenties", definition="twenty (one|two)=unit", body="twenty + unit"),
|
|
Concept("number"),
|
|
Concept("sub_number", body="number", where="number >= 20")
|
|
)
|
|
service = sheerka.services[SheerkaIsAManager.NAME]
|
|
service.add_concepts_to_set(context, [one, two, twenty, twenties], number)
|
|
|
|
assert sheerka.isaset(context, sub_number)
|
|
# compare ids, as concepts are evaluated in get_set_elements
|
|
actual_ids = set(c.id for c in sheerka.get_set_elements(context, sub_number))
|
|
expected_ids = set(c.id for c in [twenty])
|
|
assert actual_ids == expected_ids
|
|
|
|
def test_bnf_elements_can_be_part_of_a_set(self):
|
|
"""
|
|
The purpose of this test is
|
|
def concept twenties from bnf ...
|
|
twenties isa number
|
|
|
|
assert twenty two isa number
|
|
We want that all elements of the twenties are number
|
|
:return:
|
|
"""
|
|
|
|
sheerka, context, one, two, twenty, twenties, number = self.init_test().with_concepts(
|
|
Concept("one", body="1"),
|
|
Concept("two", body="2"),
|
|
Concept("twenty", body="20"),
|
|
Concept("twenties", definition="twenty (one|two)=unit", body="twenty + unit").def_var("unit"),
|
|
Concept("number"),
|
|
create_new=True).unpack()
|
|
service = sheerka.services[SheerkaIsAManager.NAME]
|
|
service.add_concepts_to_set(context, [one, two, twenty, twenties], number)
|
|
assert sheerka.isinset(twenties, number)
|
|
|
|
res = sheerka.evaluate_user_input("twenty one", "")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinset(res[0].body, number)
|
|
|
|
def test_a_concept_can_be_in_multiple_sets(self):
|
|
sheerka, context, foo, all_foo, all_bar = self.init_test().with_concepts(
|
|
Concept("foo"),
|
|
Concept("all_foo"),
|
|
Concept("all_bar"),
|
|
create_new=True).unpack()
|
|
|
|
foo = sheerka.new(foo.key) # new instance
|
|
sheerka.set_isa(context, foo, all_foo)
|
|
sheerka.set_isa(context, foo, all_bar)
|
|
|
|
assert foo.get_prop(BuiltinConcepts.ISA) == {all_foo, all_bar}
|
|
assert sheerka.isa(foo, all_foo)
|
|
assert sheerka.isa(foo, all_bar)
|
|
|
|
def test_a_concept_can_be_in_multiple_sets_when_global_truth_is_activated(self):
|
|
sheerka, context, foo, all_foo, all_bar = self.init_test(global_truth=True).with_concepts(
|
|
Concept("foo"),
|
|
Concept("all_foo"),
|
|
Concept("all_bar"),
|
|
create_new=True).unpack()
|
|
|
|
foo = sheerka.new(foo.key) # new instance
|
|
sheerka.set_isa(context, foo, all_foo)
|
|
foo = sheerka.new(foo.key) # new instance
|
|
sheerka.set_isa(context, foo, all_bar)
|
|
|
|
assert foo.get_prop(BuiltinConcepts.ISA) == {all_foo, all_bar}
|
|
assert sheerka.isa(foo, all_foo)
|
|
assert sheerka.isa(foo, all_bar)
|
|
|
|
assert sheerka.isinset(foo, all_foo)
|
|
assert sheerka.isinset(foo, all_bar)
|
|
assert sheerka.isaset(context, all_foo)
|
|
assert sheerka.isaset(context, all_bar)
|
|
|
|
def test_i_can_manage_isa_transitivity(self):
|
|
"""
|
|
if foo isa bar and bar isa baz, then foo isa baz
|
|
:return:
|
|
"""
|
|
|
|
sheerka, context, foo, bar, baz = self.init_concepts(
|
|
Concept("foo"),
|
|
Concept("bar"),
|
|
Concept("baz"),
|
|
)
|
|
|
|
sheerka.set_isa(context, foo, bar)
|
|
sheerka.set_isa(context, bar, baz)
|
|
|
|
assert sheerka.isa(foo, bar)
|
|
assert sheerka.isa(bar, baz)
|
|
assert sheerka.isa(foo, baz)
|
|
|
|
def test_i_can_manage_isa_transitivity_when_global_truth_is_activated(self):
|
|
"""
|
|
if foo isa bar and bar isa baz, then foo isa baz
|
|
:return:
|
|
"""
|
|
|
|
sheerka, context, foo, bar, baz = self.init_test(global_truth=True).with_concepts(
|
|
Concept("foo"),
|
|
Concept("bar"),
|
|
Concept("baz"),
|
|
).unpack()
|
|
|
|
sheerka.set_isa(context, sheerka.new("foo"), bar)
|
|
sheerka.set_isa(context, sheerka.new("bar"), baz)
|
|
|
|
assert sheerka.isa(sheerka.new("foo"), bar)
|
|
assert sheerka.isa(sheerka.new("bar"), baz)
|
|
assert sheerka.isa(sheerka.new("foo"), baz)
|
|
|
|
def test_i_cannot_manage_isa_transitivity_when_using_body(self):
|
|
sheerka, context, one, another_one, number = self.init_test(global_truth=True).with_concepts(
|
|
"one",
|
|
Concept("another one", body="one"),
|
|
"number"
|
|
).unpack()
|
|
|
|
sheerka.set_isa(context, sheerka.new("one"), number)
|
|
|
|
assert sheerka.isa(sheerka.new("one"), number) # sanity
|
|
assert not sheerka.isa(another_one, number) # Correct this misbehaviour when BuiltinConcepts.IS is implemented
|
|
|
|
def test_concepts_in_group_cache_is_updated(self):
|
|
sheerka, context, one, two, number = self.init_test(global_truth=True).with_concepts(
|
|
"one",
|
|
"two",
|
|
"number").unpack()
|
|
|
|
sheerka.set_isa(context, sheerka.new("one"), number)
|
|
|
|
elements = sheerka.get_set_elements(context, number)
|
|
assert [c.id for c in elements] == [one.id]
|
|
|
|
concepts_in_cache = sheerka.om.get(SheerkaIsAManager.CONCEPTS_IN_GROUPS_ENTRY, number.id)
|
|
assert [c.id for c in concepts_in_cache] == [one.id]
|
|
|
|
# pretend that number has been updated in sheerka.concepts_grammar
|
|
sheerka.get_concepts_bnf_definitions().put(number.id, "some parsing expression with 'one' only")
|
|
|
|
# add another element to number
|
|
sheerka.set_isa(context, sheerka.new("two"), number)
|
|
elements = sheerka.get_set_elements(context, number)
|
|
assert {c.id for c in elements} == {one.id, two.id}
|
|
|
|
concepts_in_cache = sheerka.om.get(SheerkaIsAManager.CONCEPTS_IN_GROUPS_ENTRY, number.id)
|
|
assert {c.id for c in concepts_in_cache} == {one.id, two.id}
|
|
|
|
# make sure the bnf definition is also updated
|
|
assert number.id not in sheerka.get_concepts_bnf_definitions()
|
|
|
|
def test_i_can_get_and_set_isa_when_multiple_ontology_layers(self):
|
|
sheerka, context, foo, group1, group2 = self.init_test(global_truth=True).with_concepts(
|
|
Concept("foo"),
|
|
Concept("group1"),
|
|
Concept("group2"),
|
|
cache_only=False
|
|
).unpack()
|
|
|
|
sheerka.set_isa(context, foo, group1)
|
|
|
|
assert sheerka.isaset(context, group1)
|
|
assert sheerka.isinset(foo, group1)
|
|
assert sheerka.isa(foo, group1)
|
|
|
|
sheerka.push_ontology(context, "new ontology")
|
|
assert sheerka.isaset(context, group1)
|
|
assert sheerka.isinset(foo, group1)
|
|
assert sheerka.isa(foo, group1)
|
|
assert not sheerka.isaset(context, group2)
|
|
assert not sheerka.isinset(foo, group2)
|
|
assert not sheerka.isa(foo, group2)
|
|
|
|
sheerka.set_isa(context, foo, group2)
|
|
assert sheerka.isaset(context, group1)
|
|
assert sheerka.isinset(foo, group1)
|
|
assert sheerka.isa(foo, group1)
|
|
assert sheerka.isaset(context, group2)
|
|
assert sheerka.isinset(foo, group2)
|
|
assert sheerka.isa(foo, group2)
|
|
|
|
# I can revert back
|
|
sheerka.pop_ontology(context)
|
|
assert sheerka.isaset(context, group1)
|
|
assert sheerka.isinset(foo, group1)
|
|
assert sheerka.isa(foo, group1)
|
|
assert not sheerka.isaset(context, group2)
|
|
assert not sheerka.isinset(foo, group2)
|
|
|
|
foo = sheerka.get_by_id(foo.id)
|
|
assert not sheerka.isa(foo, group2)
|
|
|
|
|
|
class TestSheerkaSetsManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka):
|
|
def test_i_can_add_concept_to_set_and_retrieve_it_in_another_session(self):
|
|
sheerka, context, foo, bar, group = self.init_test().with_concepts(Concept("foo"),
|
|
Concept("bar"),
|
|
Concept("group"),
|
|
create_new=True).unpack()
|
|
|
|
assert sheerka.add_concept_to_set(context, foo, group).status
|
|
sheerka.om.commit(context)
|
|
|
|
sheerka = self.get_sheerka(reset_attrs=False) # another session
|
|
context = self.get_context(sheerka)
|
|
assert sheerka.add_concept_to_set(context, bar, group).status
|
|
|
|
# I can get the elements
|
|
assert set(sheerka.get_set_elements(context, group)) == {foo, bar}
|
|
|
|
sheerka.om.commit(context) # save in db
|
|
all_entries = sheerka.om.current_sdp().get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY) # check the db
|
|
assert all_entries == {
|
|
group.id: {foo.id, bar.id}
|
|
}
|
|
|
|
# I can also add a group another elements
|
|
sheerka = self.get_sheerka(reset_attrs=False)
|
|
context = self.get_context(sheerka)
|
|
foo3 = Concept("foo3")
|
|
foo4 = Concept("foo4")
|
|
for c in [foo3, foo4]:
|
|
sheerka.create_new_concept(context, c)
|
|
|
|
sets_handler = sheerka.services[SheerkaIsAManager.NAME]
|
|
res = sets_handler.add_concepts_to_set(context, (foo3, foo4), group)
|
|
assert res.status
|
|
|
|
# I can get the elements
|
|
assert set(sheerka.get_set_elements(context, group)) == {foo, bar, foo3, foo4}
|
|
|
|
sheerka.om.commit(context) # save in db
|
|
all_entries = sheerka.om.current_sdp().get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY) # check the db
|
|
assert all_entries == {
|
|
group.id: {foo.id, bar.id, foo3.id, foo4.id}
|
|
}
|
|
|
|
def test_i_can_set_isa(self):
|
|
sheerka, context, foo, bar, group = self.init_test(global_truth=True).with_concepts("foo",
|
|
"bar",
|
|
"group",
|
|
).unpack()
|
|
|
|
# nothing was previously in ISA
|
|
foo = sheerka.new(foo.key)
|
|
assert BuiltinConcepts.ISA not in foo.get_metadata().props
|
|
res = sheerka.set_isa(context, foo, group)
|
|
assert res.status
|
|
sheerka.om.commit(context)
|
|
|
|
sheerka = self.get_sheerka(reset_attrs=False)
|
|
assert foo.get_prop(BuiltinConcepts.ISA) == {group}
|
|
assert sheerka.isa(foo, group)
|
|
assert sheerka.isinset(foo, group)
|
|
assert sheerka.isaset(context, group)
|
|
|
|
# I can do the same for bar
|
|
sheerka = self.get_sheerka(reset_attrs=False)
|
|
res = sheerka.set_isa(context, bar, group)
|
|
assert res.status
|
|
assert bar.get_prop(BuiltinConcepts.ISA) == {group}
|
|
assert sheerka.isa(bar, group)
|
|
assert sheerka.isinset(bar, group)
|
|
assert sheerka.isaset(context, group)
|
|
|
|
sheerka.om.commit(context)
|
|
|
|
# they are both in the same group
|
|
sheerka = self.get_sheerka(reset_attrs=False)
|
|
all_entries = sheerka.om.current_sdp().get(SheerkaIsAManager.CONCEPTS_GROUPS_ENTRY)
|
|
assert all_entries == {
|
|
group.id: {foo.id, bar.id}
|
|
}
|
|
|
|
elements = sheerka.get_set_elements(context, group)
|
|
assert set(elements) == {foo, bar}
|