From a14367c1010575dd9da859dfd8b16b50d7f086f5 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 10 Jun 2020 21:34:04 -0400 Subject: [PATCH 1/5] Equality defined between builtin collections respect partitions --- src/basilisp/lang/compiler/generator.py | 2 +- src/basilisp/lang/interfaces.py | 40 +++++++++------ src/basilisp/lang/keyword.py | 4 +- src/basilisp/lang/list.py | 3 -- src/basilisp/lang/map.py | 39 +++++---------- src/basilisp/lang/reader.py | 6 +-- src/basilisp/lang/runtime.py | 29 ++++++----- src/basilisp/lang/seq.py | 10 ++-- src/basilisp/lang/set.py | 9 +++- src/basilisp/lang/vector.py | 21 +++----- src/basilisp/testrunner.py | 19 +++++++- tests/basilisp/core_macros_test.lpy | 3 +- tests/basilisp/keyword_test.py | 2 - tests/basilisp/namespace_test.py | 16 +++--- tests/basilisp/runtime_test.py | 65 ++++++++++++++++++++++++- 15 files changed, 168 insertions(+), 100 deletions(-) diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index b8d4a9d0f..0e96a436c 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -228,7 +228,7 @@ def __init__( self._var_indirection_override: Deque[bool] = collections.deque([]) if logger.isEnabledFor(logging.DEBUG): # pragma: no cover - for k, v in self._opts: + for k, v in self._opts.items(): logger.debug("Compiler option %s = %s", k, v) @property diff --git a/src/basilisp/lang/interfaces.py b/src/basilisp/lang/interfaces.py index 0ad3baa82..1b9a84b67 100644 --- a/src/basilisp/lang/interfaces.py +++ b/src/basilisp/lang/interfaces.py @@ -9,6 +9,7 @@ Mapping, Optional, Sequence, + Type, TypeVar, Union, ) @@ -153,9 +154,7 @@ def empty() -> "IPersistentCollection[T]": T_assoc = TypeVar("T_assoc", bound="IAssociative") -class IAssociative( - ILookup[K, V], Mapping[K, V], IPersistentCollection[IMapEntry[K, V]] -): +class IAssociative(ILookup[K, V], IPersistentCollection[IMapEntry[K, V]]): __slots__ = () @abstractmethod @@ -193,7 +192,7 @@ class IPersistentList(ISequential, IPersistentStack[T]): T_map = TypeVar("T_map", bound="IPersistentMap") -class IPersistentMap(ICounted, IAssociative[K, V]): +class IPersistentMap(ICounted, Mapping[K, V], IAssociative[K, V]): __slots__ = () @abstractmethod @@ -267,6 +266,27 @@ def _record_lrepr(self, kwargs: Mapping) -> str: raise NotImplementedError() +def seq_equals(s1, s2) -> bool: + """Return True if two sequences contain the exactly the same elements in the + same order. Return False if one sequence is shorter than the other.""" + assert isinstance(s1, (ISeq, ISequential)) + + if not isinstance(s2, (ISeq, ISequential)): + return NotImplemented + + sentinel = object() + try: + for e1, e2 in itertools.zip_longest(s1, s2, fillvalue=sentinel): # type: ignore[arg-type] + if bool(e1 is sentinel) or bool(e2 is sentinel): + return False + if e1 != e2: + return False + except TypeError: + return False + else: + return True + + class ISeq(ILispObject, ISeqable[T]): __slots__ = () @@ -320,17 +340,9 @@ def _lrepr(self, **kwargs): return seq_lrepr(iter(self), "(", ")", **kwargs) def __eq__(self, other): - sentinel = object() - try: - for e1, e2 in itertools.zip_longest(self, other, fillvalue=sentinel): - if bool(e1 is sentinel) or bool(e2 is sentinel): - return False - if e1 != e2: - return False - except TypeError: - return False - else: + if self is other: return True + return seq_equals(self, other) def __hash__(self): return hash(tuple(self)) diff --git a/src/basilisp/lang/keyword.py b/src/basilisp/lang/keyword.py index 171f0130d..f3cebece7 100644 --- a/src/basilisp/lang/keyword.py +++ b/src/basilisp/lang/keyword.py @@ -62,13 +62,13 @@ def complete( results = filter( lambda kw: (kw.ns is not None and kw.ns == prefix) and kw.name.startswith(suffix), - kw_cache.itervalues(), + kw_cache.values(), ) else: results = filter( lambda kw: kw.name.startswith(text) or (kw.ns is not None and kw.ns.startswith(text)), - kw_cache.itervalues(), + kw_cache.values(), ) return map(str, results) diff --git a/src/basilisp/lang/list.py b/src/basilisp/lang/list.py index e187f2cbe..b593ba38d 100644 --- a/src/basilisp/lang/list.py +++ b/src/basilisp/lang/list.py @@ -22,9 +22,6 @@ def __init__(self, wrapped: "PList[T]", meta=None) -> None: self._inner = wrapped self._meta = meta - def __eq__(self, other): - return self._inner == other - def __getitem__(self, item): if isinstance(item, slice): return List(self._inner[item]) diff --git a/src/basilisp/lang/map.py b/src/basilisp/lang/map.py index 241359035..3a40044d9 100644 --- a/src/basilisp/lang/map.py +++ b/src/basilisp/lang/map.py @@ -1,4 +1,5 @@ from builtins import map as pymap +from collections.abc import ItemsView, KeysView, ValuesView from typing import Callable, Iterable, Mapping, Optional, TypeVar, Union, cast from pyrsistent import PMap, pmap # noqa # pylint: disable=unused-import @@ -44,7 +45,13 @@ def __contains__(self, item): return item in self._inner def __eq__(self, other): - return self._inner == other + if self is other: + return True + if not isinstance(other, Mapping): + return NotImplemented + if len(self._inner) != len(other): + return False + return dict(self._inner.iteritems()) == dict(other.items()) def __getitem__(self, item): return self._inner[item] @@ -53,8 +60,7 @@ def __hash__(self): return hash(self._inner) def __iter__(self): - for k, v in self._inner.iteritems(): - yield MapEntry.of(k, v) + yield from self._inner.iterkeys() def __len__(self): return len(self._inner) @@ -64,24 +70,6 @@ def _lrepr(self, **kwargs): self._inner.iteritems, start="{", end="}", meta=self._meta, **kwargs ) - def items(self): - return self._inner.items() - - def keys(self): - return self._inner.keys() - - def values(self): - return self._inner.values() - - def iteritems(self): - return self._inner.iteritems() - - def iterkeys(self): - return self._inner.iterkeys() - - def itervalues(self): - return self._inner.itervalues() - @property def meta(self) -> Optional[IPersistentMap]: return self._meta @@ -140,12 +128,7 @@ def cons( # type: ignore[override] e = self._inner.evolver() try: for elem in elems: - if isinstance(elem, (IPersistentMap, Mapping)) and not isinstance( - elem, IPersistentVector - ): - # Vectors are handled in the final else block, since we - # do not want to treat them as Mapping types for this - # particular usage. + if isinstance(elem, (IPersistentMap, Mapping)): for k, v in elem.items(): e.set(k, v) elif isinstance(elem, IMapEntry): @@ -169,7 +152,7 @@ def empty() -> "Map": return m() def seq(self) -> ISeq[IMapEntry[K, V]]: - return sequence(self) + return sequence(MapEntry.of(k, v) for k, v in self._inner.iteritems()) def map(kvs: Mapping[K, V], meta=None) -> Map[K, V]: # pylint:disable=redefined-builtin diff --git a/src/basilisp/lang/reader.py b/src/basilisp/lang/reader.py index 949102049..0867dd9d7 100644 --- a/src/basilisp/lang/reader.py +++ b/src/basilisp/lang/reader.py @@ -296,10 +296,10 @@ def __init__( # pylint: disable=too-many-arguments process_reader_cond: bool = True, ) -> None: data_readers = Maybe(data_readers).or_else_get(lmap.Map.empty()) - for entry in data_readers: - if not isinstance(entry.key, symbol.Symbol): + for reader_sym in data_readers.keys(): + if not isinstance(reader_sym, symbol.Symbol): raise TypeError("Expected symbol for data reader tag") - if not entry.key.ns: + if not reader_sym.ns: raise ValueError(f"Non-namespaced tags are reserved by the reader") self._data_readers = ReaderContext._DATA_READERS.update_with( diff --git a/src/basilisp/lang/runtime.py b/src/basilisp/lang/runtime.py index 64803aa8c..4b6f616a5 100644 --- a/src/basilisp/lang/runtime.py +++ b/src/basilisp/lang/runtime.py @@ -639,9 +639,7 @@ def refer_all(self, other_ns: "Namespace") -> None: """Refer all the Vars in the other namespace.""" with self._lock: final_refers = self._refers - for entry in other_ns.interns: - s: sym.Symbol = entry.key - var: Var = entry.value + for s, var in other_ns.interns.items(): if not var.is_private: final_refers = final_refers.assoc(s, var) self._refers = final_refers @@ -716,7 +714,8 @@ def __complete_alias( prefix from the list of aliased namespaces. If name_in_ns is given, further attempt to refine the list to matching names in that namespace.""" candidates = filter( - Namespace.__completion_matcher(prefix), ((s, n) for s, n in self.aliases) + Namespace.__completion_matcher(prefix), + ((s, n) for s, n in self.aliases.items()), ) if name_in_ns is not None: for _, candidate_ns in candidates: @@ -739,12 +738,13 @@ def __complete_imports_and_aliases( aliases = lmap.map( { alias: imports.val_at(import_name) - for alias, import_name in self.import_aliases + for alias, import_name in self.import_aliases.items() } ) candidates = filter( - Namespace.__completion_matcher(prefix), itertools.chain(aliases, imports) + Namespace.__completion_matcher(prefix), + itertools.chain(aliases.items(), imports.items()), ) if name_in_module is not None: for _, module in candidates: @@ -771,7 +771,7 @@ def is_match(entry: Tuple[sym.Symbol, Var]) -> bool: return map( lambda entry: f"{entry[0].name}", - filter(is_match, ((s, v) for s, v in self.interns)), + filter(is_match, ((s, v) for s, v in self.interns.items())), ) # pylint: disable=unnecessary-comprehension @@ -781,7 +781,8 @@ def __complete_refers(self, value: str) -> Iterable[str]: return map( lambda entry: f"{entry[0].name}", filter( - Namespace.__completion_matcher(value), ((s, v) for s, v in self.refers) + Namespace.__completion_matcher(value), + ((s, v) for s, v in self.refers.items()), ), ) @@ -807,13 +808,11 @@ def complete(self, text: str) -> Iterable[str]: return results -def push_thread_bindings(m: IAssociative[Var, Any]) -> None: +def push_thread_bindings(m: IPersistentMap[Var, Any]) -> None: """Push thread local bindings for the Var keys in m using the values.""" bindings = set() - for entry in m: - var: Var = entry.key # type: ignore - val = entry.value + for var, val in m.items(): if not var.dynamic: raise RuntimeException( "cannot set thread-local bindings for non-dynamic Var" @@ -853,11 +852,11 @@ def first(o): return s.first -def rest(o) -> Optional[ISeq]: +def rest(o) -> ISeq: """If o is a ISeq, return the elements after the first in o. If o is None, returns an empty seq. Otherwise, coerces o to a seq and returns the rest.""" if o is None: - return None + return lseq.EMPTY if isinstance(o, ISeq): s = o.rest if s is None: @@ -1233,7 +1232,7 @@ def _to_py_map( ) -> dict: return { to_py(key, keyword_fn=keyword_fn): to_py(value, keyword_fn=keyword_fn) - for key, value in o + for key, value in o.items() } diff --git a/src/basilisp/lang/seq.py b/src/basilisp/lang/seq.py index 63abdb401..4f846dc7c 100644 --- a/src/basilisp/lang/seq.py +++ b/src/basilisp/lang/seq.py @@ -1,12 +1,12 @@ from typing import Any, Callable, Iterable, Iterator, Optional, TypeVar -from basilisp.lang.interfaces import IPersistentMap, ISeq, IWithMeta +from basilisp.lang.interfaces import IPersistentMap, ISeq, ISequential, IWithMeta from basilisp.util import Maybe T = TypeVar("T") -class _EmptySequence(IWithMeta, ISeq[T]): +class _EmptySequence(IWithMeta, ISequential, ISeq[T]): def __repr__(self): return "()" @@ -39,7 +39,7 @@ def cons(self, elem: T) -> ISeq[T]: EMPTY: ISeq = _EmptySequence() -class Cons(ISeq[T], IWithMeta): +class Cons(ISeq[T], ISequential, IWithMeta): __slots__ = ("_first", "_rest", "_meta") def __init__( @@ -75,7 +75,7 @@ def with_meta(self, meta: Optional[IPersistentMap]) -> "Cons[T]": return Cons(first=self._first, seq=self._rest, meta=meta) -class _Sequence(IWithMeta, ISeq[T]): +class _Sequence(IWithMeta, ISequential, ISeq[T]): """Sequences are a thin wrapper over Python Iterable values so they can satisfy the Basilisp `ISeq` interface. @@ -128,7 +128,7 @@ def cons(self, elem): return Cons(elem, self) -class LazySeq(IWithMeta, ISeq[T]): +class LazySeq(IWithMeta, ISequential, ISeq[T]): """LazySeqs are wrappers for delaying sequence computation. Create a LazySeq with a function that can either return None or a Seq. If a Seq is returned, the LazySeq is a proxy to that Seq.""" diff --git a/src/basilisp/lang/set.py b/src/basilisp/lang/set.py index 9c3c5bf04..a4da2345e 100644 --- a/src/basilisp/lang/set.py +++ b/src/basilisp/lang/set.py @@ -1,4 +1,5 @@ -from typing import Iterable, Optional, TypeVar +from collections.abc import Set as _PySet +from typing import AbstractSet, Iterable, Optional, TypeVar from pyrsistent import PSet, pset # noqa # pylint: disable=unused-import @@ -36,7 +37,11 @@ def __contains__(self, item): return item in self._inner def __eq__(self, other): - return self._inner == other + if self is other: + return True + if not isinstance(other, AbstractSet): + return NotImplemented + return _PySet.__eq__(self, other) def __ge__(self, other): return self._inner >= other diff --git a/src/basilisp/lang/vector.py b/src/basilisp/lang/vector.py index 0659a2dcd..37c11f860 100644 --- a/src/basilisp/lang/vector.py +++ b/src/basilisp/lang/vector.py @@ -8,7 +8,9 @@ IPersistentMap, IPersistentVector, ISeq, + ISequential, IWithMeta, + seq_equals, ) from basilisp.lang.obj import seq_lrepr as _seq_lrepr from basilisp.lang.seq import sequence @@ -33,12 +35,11 @@ def __contains__(self, item): return item in self._inner def __eq__(self, other): - if hasattr(other, "__len__"): - return self._inner == other - try: - return all(e1 == e2 for e1, e2 in zip(self._inner, other)) - except TypeError: + if self is other: + return True + if hasattr(other, "__len__") and len(self) != len(other): return False + return seq_equals(self, other) def __getitem__(self, item): if isinstance(item, slice): @@ -106,15 +107,7 @@ def pop(self) -> "Vector[T]": return self[:-1] def rseq(self) -> ISeq[T]: - def _reverse_vec() -> Iterable[T]: - l = len(self) - for i in range(l - 1, 0, -1): - yield self._inner[i] - - if l: - yield self._inner[0] - - return sequence(_reverse_vec()) + return sequence(reversed(self)) K = TypeVar("K") diff --git a/src/basilisp/testrunner.py b/src/basilisp/testrunner.py index a39e434d2..3fd2dc9ed 100644 --- a/src/basilisp/testrunner.py +++ b/src/basilisp/testrunner.py @@ -104,7 +104,9 @@ def collect(self): ns = module.__basilisp_namespace__ for test in self._collected_tests(ns): f: TestFunction = test.value - yield BasilispTestItem(test.name.name, self, f, ns, filename) + yield BasilispTestItem.from_parent( # type: ignore[call-arg] + self, name=test.name.name, run_test=f, namespace=ns, filename=filename + ) _ACTUAL_KW = kw.keyword("actual") @@ -142,6 +144,21 @@ def __init__( # pylint: disable=too-many-arguments self._namespace = namespace self._filename = filename + @classmethod + def from_parent( + cls, + parent: "BasilispFile", + name: str, + run_test: TestFunction, + namespace: runtime.Namespace, + filename: str, + ): + """Create a new BasilispTestItem from the parent Node.""" + # https://github.com/pytest-dev/pytest/pull/6680 + return super().from_parent( + parent, name=name, run_test=run_test, namespace=namespace, filename=filename + ) + def runtest(self): """Run the tests associated with this test item. diff --git a/tests/basilisp/core_macros_test.lpy b/tests/basilisp/core_macros_test.lpy index fefce411d..dbc58c6f6 100644 --- a/tests/basilisp/core_macros_test.lpy +++ b/tests/basilisp/core_macros_test.lpy @@ -402,7 +402,8 @@ [x y])))) (testing "for comprehension with :while modifier" - (is (= [[0 :a] [0 :b] [0 :c]] + (is (= [[0 :a] [0 :b] [0 :c] + [2 :a] [2 :b] [2 :c]] (for [x (range 3) y [:a :b :c] :while (even? x)] diff --git a/tests/basilisp/keyword_test.py b/tests/basilisp/keyword_test.py index ccc9d47e9..5fae6f681 100644 --- a/tests/basilisp/keyword_test.py +++ b/tests/basilisp/keyword_test.py @@ -1,10 +1,8 @@ import pickle import pytest -from pyrsistent import PMap, pmap import basilisp.lang.map as lmap -from basilisp.lang.atom import Atom from basilisp.lang.keyword import Keyword, complete, keyword diff --git a/tests/basilisp/namespace_test.py b/tests/basilisp/namespace_test.py index 960c71951..8fddc884d 100644 --- a/tests/basilisp/namespace_test.py +++ b/tests/basilisp/namespace_test.py @@ -38,11 +38,11 @@ def ns_sym() -> sym.Symbol: def test_create_ns(ns_sym: sym.Symbol, ns_cache: atom.Atom[NamespaceMap]): - assert len(ns_cache.deref().keys()) == 1 + assert len(list(ns_cache.deref().keys())) == 1 ns = get_or_create_ns(ns_sym) assert isinstance(ns, Namespace) assert ns.name == ns_sym.name - assert len(ns_cache.deref().keys()) == 2 + assert len(list(ns_cache.deref().keys())) == 2 @pytest.fixture @@ -60,21 +60,21 @@ def ns_cache_with_existing_ns( def test_get_existing_ns( ns_sym: sym.Symbol, ns_cache_with_existing_ns: atom.Atom[NamespaceMap] ): - assert len(ns_cache_with_existing_ns.deref().keys()) == 2 + assert len(list(ns_cache_with_existing_ns.deref().keys())) == 2 ns = get_or_create_ns(ns_sym) assert isinstance(ns, Namespace) assert ns.name == ns_sym.name - assert len(ns_cache_with_existing_ns.deref().keys()) == 2 + assert len(list(ns_cache_with_existing_ns.deref().keys())) == 2 def test_remove_ns( ns_sym: sym.Symbol, ns_cache_with_existing_ns: atom.Atom[NamespaceMap] ): - assert len(ns_cache_with_existing_ns.deref().keys()) == 2 + assert len(list(ns_cache_with_existing_ns.deref().keys())) == 2 ns = Namespace.remove(ns_sym) assert isinstance(ns, Namespace) assert ns.name == ns_sym.name - assert len(ns_cache_with_existing_ns.deref().keys()) == 1 + assert len(list(ns_cache_with_existing_ns.deref().keys())) == 1 @pytest.fixture @@ -85,10 +85,10 @@ def other_ns_sym() -> sym.Symbol: def test_remove_non_existent_ns( other_ns_sym: sym.Symbol, ns_cache_with_existing_ns: patch ): - assert len(ns_cache_with_existing_ns.deref().keys()) == 2 + assert len(list(ns_cache_with_existing_ns.deref().keys())) == 2 ns = Namespace.remove(other_ns_sym) assert ns is None - assert len(ns_cache_with_existing_ns.deref().keys()) == 2 + assert len(list(ns_cache_with_existing_ns.deref().keys())) == 2 def test_alter_ns_meta( diff --git a/tests/basilisp/runtime_test.py b/tests/basilisp/runtime_test.py index 2a7064561..566adbd5e 100644 --- a/tests/basilisp/runtime_test.py +++ b/tests/basilisp/runtime_test.py @@ -1,3 +1,6 @@ +from decimal import Decimal +from fractions import Fraction + import pytest import basilisp.lang.atom as atom @@ -25,7 +28,7 @@ def test_first(): def test_rest(): - assert None is runtime.rest(None) + assert lseq.EMPTY is runtime.rest(None) assert lseq.EMPTY is runtime.rest(llist.l()) assert lseq.EMPTY is runtime.rest(llist.l(1)) assert llist.l(2, 3) == runtime.rest(llist.l(1, 2, 3)) @@ -271,6 +274,66 @@ def test_deref(): runtime.deref(vec.Vector.empty()) +@pytest.mark.parametrize( + "v1,v2", + [ + (0, 0), + (1, 1), + (-1, -1), + (0.0, 0.0), + (1.0, 1.0), + (-1.0, -1.0), + (0.0, 0), + (1.0, 1), + (-1.0, -1), + (True, True), + (False, False), + (None, None), + ("", ""), + ("not empty", "not empty"), + (Fraction("1/2"), Fraction("1/2")), + (Decimal("3.14159"), Decimal("3.14159")), + (llist.List.empty(), llist.List.empty()), + (lmap.Map.empty(), lmap.Map.empty()), + (lset.Set.empty(), lset.Set.empty()), + (vec.Vector.empty(), vec.Vector.empty()), + (lseq.EMPTY, lseq.EMPTY), + (llist.List.empty(), vec.Vector.empty()), + (lseq.EMPTY, vec.Vector.empty()), + (llist.List.empty(), lseq.EMPTY), + (lset.Set.empty(), lset.Set.empty()), + (lmap.Map.empty(), lmap.Map.empty()), + ], +) +def test_equals(v1, v2): + assert runtime.equals(v1, v2) + assert runtime.equals(v2, v1) + + +@pytest.mark.parametrize( + "v1,v2", + [ + (1, True), + (0, False), + ("", "not empty"), + (1, -1), + (0, 0.00000032), + (llist.l(1, 2, 3), llist.l(2, 3, 4)), + (llist.l(1, 2, 3), vec.v(2, 3, 4)), + (lmap.Map.empty(), llist.List.empty()), + (lmap.Map.empty(), vec.Vector.empty()), + (lmap.Map.empty(), lseq.EMPTY), + (lset.Set.empty(), llist.List.empty()), + (lset.Set.empty(), lmap.Map.empty()), + (lset.Set.empty(), vec.Vector.empty()), + (lset.Set.empty(), lseq.EMPTY), + ], +) +def test_not_equals(v1, v2): + assert not runtime.equals(v1, v2) + assert not runtime.equals(v2, v1) + + def test_pop_thread_bindings(): with pytest.raises(runtime.RuntimeException): runtime.pop_thread_bindings() From eec0e18dc71ff97ef553f03052314b4dbc91aa3b Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 10 Jun 2020 21:52:19 -0400 Subject: [PATCH 2/5] Test more type membership --- src/basilisp/lang/reader.py | 2 +- tests/basilisp/list_test.py | 13 ++++++++++++- tests/basilisp/map_test.py | 8 +++++--- tests/basilisp/set_test.py | 11 ++++++++++- tests/basilisp/vector_test.py | 16 +++++++++++++--- 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/basilisp/lang/reader.py b/src/basilisp/lang/reader.py index 0867dd9d7..b18d4c4c1 100644 --- a/src/basilisp/lang/reader.py +++ b/src/basilisp/lang/reader.py @@ -300,7 +300,7 @@ def __init__( # pylint: disable=too-many-arguments if not isinstance(reader_sym, symbol.Symbol): raise TypeError("Expected symbol for data reader tag") if not reader_sym.ns: - raise ValueError(f"Non-namespaced tags are reserved by the reader") + raise ValueError("Non-namespaced tags are reserved by the reader") self._data_readers = ReaderContext._DATA_READERS.update_with( lambda l, r: l, # Do not allow callers to overwrite existing builtin readers diff --git a/tests/basilisp/list_test.py b/tests/basilisp/list_test.py index ebdb1cbd9..1b4314240 100644 --- a/tests/basilisp/list_test.py +++ b/tests/basilisp/list_test.py @@ -8,8 +8,10 @@ ILispObject, IPersistentCollection, IPersistentList, + IPersistentStack, ISeq, ISeqable, + ISequential, IWithMeta, ) from basilisp.lang.keyword import keyword @@ -18,7 +20,16 @@ @pytest.mark.parametrize( "interface", - [IPersistentCollection, IPersistentList, IWithMeta, ILispObject, ISeq, ISeqable,], + [ + ILispObject, + IPersistentCollection, + IPersistentList, + IPersistentStack, + ISeq, + ISeqable, + ISequential, + IWithMeta, + ], ) def test_list_interface_membership(interface): assert isinstance(llist.l(), interface) diff --git a/tests/basilisp/map_test.py b/tests/basilisp/map_test.py index 3fc2dfca9..83d69a4fb 100644 --- a/tests/basilisp/map_test.py +++ b/tests/basilisp/map_test.py @@ -6,6 +6,7 @@ import basilisp.lang.map as lmap from basilisp.lang.interfaces import ( IAssociative, + ICounted, ILispObject, IMapEntry, IPersistentCollection, @@ -27,12 +28,13 @@ def test_map_entry_interface_membership(): "interface", [ IAssociative, + ICounted, + ILispObject, IPersistentCollection, - IWithMeta, IPersistentMap, - Mapping, - ILispObject, ISeqable, + IWithMeta, + Mapping, ], ) def test_map_interface_membership(interface): diff --git a/tests/basilisp/set_test.py b/tests/basilisp/set_test.py index 82ac4d57a..67bd04498 100644 --- a/tests/basilisp/set_test.py +++ b/tests/basilisp/set_test.py @@ -6,6 +6,7 @@ import basilisp.lang.map as lmap import basilisp.lang.set as lset from basilisp.lang.interfaces import ( + ICounted, ILispObject, IPersistentCollection, IPersistentSet, @@ -18,7 +19,15 @@ @pytest.mark.parametrize( "interface", - [IPersistentCollection, IWithMeta, ILispObject, ISeqable, IPersistentSet], + [ + typing.AbstractSet, + ICounted, + ILispObject, + IPersistentCollection, + IPersistentSet, + ISeqable, + IWithMeta, + ], ) def test_set_interface_membership(interface): assert isinstance(lset.s(), interface) diff --git a/tests/basilisp/vector_test.py b/tests/basilisp/vector_test.py index 74ff06cf7..a04488919 100644 --- a/tests/basilisp/vector_test.py +++ b/tests/basilisp/vector_test.py @@ -6,10 +6,15 @@ import basilisp.lang.vector as vector from basilisp.lang.interfaces import ( IAssociative, + ICounted, ILispObject, + ILookup, IPersistentCollection, + IPersistentStack, IPersistentVector, + IReversible, ISeqable, + ISequential, IWithMeta, ) from basilisp.lang.keyword import keyword @@ -20,11 +25,16 @@ "interface", [ IAssociative, - IPersistentCollection, - IWithMeta, + ICounted, ILispObject, - ISeqable, + ILookup, + IPersistentCollection, + IPersistentStack, IPersistentVector, + IReversible, + ISeqable, + ISequential, + IWithMeta, ], ) def test_vector_interface_membership(interface): From 8e9cb8631d6a2752d5d619420c2668754497cd98 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 10 Jun 2020 22:23:21 -0400 Subject: [PATCH 3/5] Fix things --- src/basilisp/lang/interfaces.py | 1 - src/basilisp/lang/map.py | 1 - src/basilisp/lang/vector.py | 1 - src/basilisp/testrunner.py | 4 ++-- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/basilisp/lang/interfaces.py b/src/basilisp/lang/interfaces.py index 1b9a84b67..74d7f1986 100644 --- a/src/basilisp/lang/interfaces.py +++ b/src/basilisp/lang/interfaces.py @@ -9,7 +9,6 @@ Mapping, Optional, Sequence, - Type, TypeVar, Union, ) diff --git a/src/basilisp/lang/map.py b/src/basilisp/lang/map.py index 3a40044d9..cfff0ee0c 100644 --- a/src/basilisp/lang/map.py +++ b/src/basilisp/lang/map.py @@ -1,5 +1,4 @@ from builtins import map as pymap -from collections.abc import ItemsView, KeysView, ValuesView from typing import Callable, Iterable, Mapping, Optional, TypeVar, Union, cast from pyrsistent import PMap, pmap # noqa # pylint: disable=unused-import diff --git a/src/basilisp/lang/vector.py b/src/basilisp/lang/vector.py index 37c11f860..79d7381a6 100644 --- a/src/basilisp/lang/vector.py +++ b/src/basilisp/lang/vector.py @@ -8,7 +8,6 @@ IPersistentMap, IPersistentVector, ISeq, - ISequential, IWithMeta, seq_equals, ) diff --git a/src/basilisp/testrunner.py b/src/basilisp/testrunner.py index 3fd2dc9ed..aea0e2d79 100644 --- a/src/basilisp/testrunner.py +++ b/src/basilisp/testrunner.py @@ -104,7 +104,7 @@ def collect(self): ns = module.__basilisp_namespace__ for test in self._collected_tests(ns): f: TestFunction = test.value - yield BasilispTestItem.from_parent( # type: ignore[call-arg] + yield BasilispTestItem.from_parent( self, name=test.name.name, run_test=f, namespace=ns, filename=filename ) @@ -145,7 +145,7 @@ def __init__( # pylint: disable=too-many-arguments self._filename = filename @classmethod - def from_parent( + def from_parent( # pylint: disable=arguments-differ,too-many-arguments cls, parent: "BasilispFile", name: str, From 2d6ef62aef4157b62ab95feed22ac2b44931d611 Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Wed, 10 Jun 2020 22:31:47 -0400 Subject: [PATCH 4/5] more equals cases --- tests/basilisp/runtime_test.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/basilisp/runtime_test.py b/tests/basilisp/runtime_test.py index 566adbd5e..561bc53ed 100644 --- a/tests/basilisp/runtime_test.py +++ b/tests/basilisp/runtime_test.py @@ -294,15 +294,22 @@ def test_deref(): (Fraction("1/2"), Fraction("1/2")), (Decimal("3.14159"), Decimal("3.14159")), (llist.List.empty(), llist.List.empty()), + (llist.l(1, 2, 3), llist.l(1, 2, 3)), (lmap.Map.empty(), lmap.Map.empty()), + (lmap.map({"a": 1, "b": 2}), lmap.map({"a": 1, "b": 2})), (lset.Set.empty(), lset.Set.empty()), + (lset.s(1, 2, 3), lset.s(1, 2, 3)), (vec.Vector.empty(), vec.Vector.empty()), + (vec.v(1, 2, 3), vec.v(1, 2, 3)), (lseq.EMPTY, lseq.EMPTY), + (lseq.EMPTY.cons(3).cons(2).cons(1), lseq.EMPTY.cons(3).cons(2).cons(1)), + (vec.v(1, 2, 3), lseq.EMPTY.cons(3).cons(2).cons(1)), (llist.List.empty(), vec.Vector.empty()), + (llist.l(1, 2, 3), vec.v(1, 2, 3)), (lseq.EMPTY, vec.Vector.empty()), + (lseq.EMPTY.cons(3).cons(2).cons(1), vec.v(1, 2, 3)), (llist.List.empty(), lseq.EMPTY), - (lset.Set.empty(), lset.Set.empty()), - (lmap.Map.empty(), lmap.Map.empty()), + (lseq.EMPTY.cons(3).cons(2).cons(1), llist.l(1, 2, 3)), ], ) def test_equals(v1, v2): @@ -323,10 +330,17 @@ def test_equals(v1, v2): (lmap.Map.empty(), llist.List.empty()), (lmap.Map.empty(), vec.Vector.empty()), (lmap.Map.empty(), lseq.EMPTY), + (lmap.map({1: "1", 2: "2", 3: "3"}), llist.l(1, 2, 3)), + (lmap.map({1: "1", 2: "2", 3: "3"}), vec.v(1, 2, 3)), + (lmap.map({1: "1", 2: "2", 3: "3"}), lseq.EMPTY.cons(3).cons(2).cons(1)), (lset.Set.empty(), llist.List.empty()), (lset.Set.empty(), lmap.Map.empty()), (lset.Set.empty(), vec.Vector.empty()), (lset.Set.empty(), lseq.EMPTY), + (lset.s(1, 2, 3), llist.l(1, 2, 3)), + (lset.s(1, 2, 3), lmap.Map.empty()), + (lset.s(1, 2, 3), vec.v(1, 2, 3)), + (lset.s(1, 2, 3), lseq.EMPTY.cons(3).cons(2).cons(1)), ], ) def test_not_equals(v1, v2): From 83c92bb030724bf140298b4cd7f491b65bbbe63f Mon Sep 17 00:00:00 2001 From: Christopher Rink Date: Thu, 11 Jun 2020 08:33:39 -0400 Subject: [PATCH 5/5] Coverage --- src/basilisp/lang/interfaces.py | 16 ++++++---------- tests/basilisp/map_test.py | 10 ++++++++-- tests/basilisp/vector_test.py | 10 ++++++++++ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/basilisp/lang/interfaces.py b/src/basilisp/lang/interfaces.py index 74d7f1986..360017cd5 100644 --- a/src/basilisp/lang/interfaces.py +++ b/src/basilisp/lang/interfaces.py @@ -274,16 +274,12 @@ def seq_equals(s1, s2) -> bool: return NotImplemented sentinel = object() - try: - for e1, e2 in itertools.zip_longest(s1, s2, fillvalue=sentinel): # type: ignore[arg-type] - if bool(e1 is sentinel) or bool(e2 is sentinel): - return False - if e1 != e2: - return False - except TypeError: - return False - else: - return True + for e1, e2 in itertools.zip_longest(s1, s2, fillvalue=sentinel): # type: ignore[arg-type] + if bool(e1 is sentinel) or bool(e2 is sentinel): + return False + if e1 != e2: + return False + return True class ISeq(ILispObject, ISeqable[T]): diff --git a/tests/basilisp/map_test.py b/tests/basilisp/map_test.py index 83d69a4fb..f3543f6a2 100644 --- a/tests/basilisp/map_test.py +++ b/tests/basilisp/map_test.py @@ -16,7 +16,7 @@ ) from basilisp.lang.keyword import keyword from basilisp.lang.symbol import symbol -from basilisp.lang.vector import MapEntry +from basilisp.lang.vector import MapEntry, v def test_map_entry_interface_membership(): @@ -74,6 +74,12 @@ def test_dissoc(): def test_entry(): + assert MapEntry.of("a", 1) == lmap.map({"a": 1}).entry("a") + assert None is lmap.map({"a": 1}).entry("b") + assert None is lmap.Map.empty().entry("a") + + +def test_val_at(): assert 1 == lmap.map({"a": 1}).val_at("a") assert None is lmap.map({"a": 1}).val_at("b") assert None is lmap.Map.empty().val_at("a") @@ -121,7 +127,7 @@ def test_map_cons(): meta = lmap.m(tag="async") m1 = lmap.map({"first": "Chris"}, meta=meta) - m2 = m1.cons(["last", "Cronk"], ["middle", "L"]) + m2 = m1.cons(["last", "Cronk"], v("middle", "L")) assert m1 is not m2 assert m1 != m2 assert len(m2) == 3 diff --git a/tests/basilisp/vector_test.py b/tests/basilisp/vector_test.py index a04488919..c75f69d90 100644 --- a/tests/basilisp/vector_test.py +++ b/tests/basilisp/vector_test.py @@ -88,6 +88,16 @@ def test_vector_cons(): def test_entry(): + assert vector.MapEntry.of(0, "a") == vector.v("a").entry(0) + assert vector.MapEntry.of(1, "b") == vector.v("a", "b").entry(1) + assert None is vector.v("a", "b").entry(2) + assert vector.MapEntry.of(-1, "b") == vector.v("a", "b").entry(-1) + assert None is vector.Vector.empty().entry(0) + assert None is vector.Vector.empty().entry(1) + assert None is vector.Vector.empty().entry(-1) + + +def test_val_at(): assert "a" == vector.v("a").val_at(0) assert "b" == vector.v("a", "b").val_at(1) assert None is vector.v("a", "b").val_at(2)