-
Notifications
You must be signed in to change notification settings - Fork 0
Add testing support for Pyodide and MicroPython environments #17
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
base: master
Are you sure you want to change the base?
Changes from 6 commits
f3705ef
a710851
107f9b5
bef22f8
6abe6e9
02689e3
736b0dc
21a19d4
e819cb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import sys | ||
|
|
||
| def modify_core(filepath): | ||
| with open(filepath, 'r') as f: | ||
| content = f.read() | ||
|
|
||
| # We will just use replace_with_git_merge_diff below. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import sys | ||
| import os | ||
|
|
||
| # Ensure src is in the path | ||
| sys.path.append(os.getcwd() + '/src') | ||
| sys.path.append(os.getcwd() + '/test') | ||
|
|
||
| import test_micropython_core | ||
| test_micropython_core.run_all_tests() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,74 @@ | ||
| import inspect | ||
| from abc import ABCMeta, abstractmethod | ||
| from collections.abc import Iterable, Mapping | ||
| from itertools import chain | ||
| from pprint import pformat | ||
| try: | ||
| from typing import Any | ||
| except ImportError: # MicroPython compatibility fallback for missing typing | ||
| Any = object # type: ignore[assignment,misc] | ||
|
|
||
| from closure_collector.util import ClosureCollectorException, is_rule, rebind | ||
| try: | ||
| import inspect | ||
| except ImportError: # MicroPython compatibility fallback for missing inspect | ||
| inspect = None # type: ignore[assignment] | ||
|
|
||
| try: | ||
| from collections.abc import Callable | ||
| except ImportError: # MicroPython compatibility fallback for missing collections.abc | ||
| Callable = object # type: ignore[assignment,misc] | ||
|
|
||
| try: | ||
| from typing import TypeVar | ||
| except ImportError: # MicroPython compatibility fallback for missing typing | ||
|
|
||
| def TypeVar(name: str, bound: Any = Any) -> Any: # type: ignore[misc,no-redef] | ||
| return object | ||
|
|
||
|
|
||
| try: | ||
| _FuncT = TypeVar("_FuncT", bound=Callable[..., Any]) | ||
| except TypeError: | ||
| _FuncT = object # type: ignore[assignment,misc] | ||
|
|
||
| try: | ||
| from abc import ABCMeta, abstractmethod | ||
| except ImportError: # MicroPython compatibility fallback for missing abc | ||
|
|
||
| class ABCMeta(type): # type: ignore[no-redef] | ||
| pass | ||
|
|
||
| def abstractmethod(funcobj: _FuncT) -> _FuncT: # noqa: UP047 | ||
| return funcobj # type: ignore[misc] | ||
|
|
||
|
|
||
| try: | ||
| from collections.abc import Iterable, Mapping | ||
| except ImportError: # MicroPython compatibility fallback for missing collections.abc | ||
| try: | ||
| from collections.abc import Iterable, Mapping | ||
| except ImportError: # MicroPython compatibility fallback for missing collections.abc | ||
| Iterable = object # type: ignore[assignment,misc] | ||
| Mapping = object # type: ignore[assignment,misc] | ||
|
|
||
| try: | ||
| from itertools import chain | ||
| except ImportError: # MicroPython compatibility fallback for missing itertools | ||
|
|
||
| class chain: # type: ignore[no-redef] | ||
| def __init__(self, *iterables: Any): | ||
| self.iterables = iterables | ||
|
|
||
| def __iter__(self) -> Any: | ||
| for it in self.iterables: | ||
| yield from it | ||
|
|
||
| @classmethod | ||
| def from_iterable(cls, iterables: Any) -> Any: | ||
| return cls(*iterables) | ||
|
|
||
|
|
||
| try: | ||
| from pprint import pformat | ||
| except ImportError: # MicroPython compatibility fallback for missing pprint | ||
| pformat = repr # type: ignore[assignment] | ||
|
|
||
| from closure_collector.util import ClosureCollectorException, get_cell_contents, is_rule, is_zero_arg, rebind # noqa: E402 | ||
|
|
||
| CLOSURE_ATTRS = {"root", "cache", "peers", "promises"} | ||
|
|
||
|
|
@@ -19,46 +83,73 @@ def __str__(self): | |
| return pformat(self.__dict__) | ||
|
|
||
|
|
||
| class CCBase(metaclass=ABCMeta): | ||
| """Base class for Closure Collector Objects of all sorts""" | ||
| if hasattr(ABCMeta, "__new__"): | ||
|
|
||
| @abstractmethod | ||
| def check(self, path): | ||
| """ | ||
| check for any contents that would prevent this Aggregator from being used normally, esp sheared. | ||
| :type path: list the path to this object, will be prepended to any errors generated | ||
| :return: list of errors that prevent items in this Aggregator from being sheared. | ||
| """ | ||
| class CCBase(metaclass=ABCMeta): | ||
| """Base class for Closure Collector Objects of all sorts""" | ||
|
|
||
| @abstractmethod | ||
| def shear(self, record_errors=False): | ||
| """ | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you remove this docstring?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry about that! It was accidentally removed during the refactoring process for the MicroPython feature detection fallbacks. I've restored the docstrings in both |
||
| Convert this closure collection into a simple object | ||
| @abstractmethod | ||
| def check(self, path): | ||
| """ | ||
| check for any contents that would prevent this Aggregator from being used normally, esp sheared. | ||
| :type path: list the path to this object, will be prepended to any errors generated | ||
| :return: list of errors that prevent items in this Aggregator from being sheared. | ||
| """ | ||
| pass | ||
|
|
||
| :param record_errors: if True any exception raised will be stored in place of the result that caused it rather | ||
| than continuing up the call stack | ||
| @abstractmethod | ||
| def shear(self, record_errors=False): | ||
| """ | ||
| Convert this closure collection into a simple object | ||
|
|
||
| :return: a simple object representing these closures | ||
| """ | ||
| :param record_errors: if True any exception raised will be stored in place of the result that caused it rather | ||
| than continuing up the call stack | ||
|
|
||
| @abstractmethod | ||
| def __dir__(self): | ||
| """Closure collector objects all support the dir() method returning the added attributes""" | ||
| pass | ||
| :return: a simple object representing these closures | ||
| """ | ||
| pass | ||
|
|
||
| def __call__(self): | ||
| """ | ||
| Call must be specified so that Closure Collections can be nested within eachother | ||
| @abstractmethod | ||
| def __dir__(self): | ||
| """Closure collector objects all support the dir() method returning the added attributes""" | ||
| pass | ||
|
|
||
| :return: self | ||
| """ | ||
| return self | ||
| def __call__(self): | ||
| """ | ||
| Call must be specified so that Closure Collections can be nested within eachother | ||
|
|
||
| def clear_cache(self): | ||
| """Empty any cache kept on this object""" | ||
| :return: self | ||
| """ | ||
| return self | ||
|
|
||
| def get_relatives(self) -> Iterable: | ||
| return () | ||
| def clear_cache(self): | ||
| """Empty any cache kept on this object""" | ||
| pass | ||
|
|
||
| def get_relatives(self) -> Iterable: | ||
| return () | ||
| else: | ||
|
|
||
| class CCBase: # type: ignore[no-redef] | ||
| """Base class for Closure Collector Objects of all sorts""" | ||
|
|
||
| def check(self, path): | ||
| pass | ||
|
|
||
| def shear(self, record_errors=False): | ||
| pass | ||
|
|
||
| def __dir__(self): | ||
| return [] | ||
|
|
||
| def __call__(self): | ||
| return self | ||
|
|
||
| def clear_cache(self): | ||
| pass | ||
|
|
||
| def get_relatives(self) -> Iterable: | ||
| return () | ||
|
|
||
|
|
||
| class DynamicClosureCollector(CCBase): | ||
|
|
@@ -147,17 +238,18 @@ def __bool__(self): | |
| return bool(self.promises) | ||
|
|
||
| def make_callable(self, value): | ||
| if callable(value) and len(inspect.signature(value).parameters) == 0: | ||
| if is_zero_arg(value): | ||
| ret = value | ||
| if isinstance(value, DynamicClosureCollector): | ||
| value.peers.add(self) | ||
| if value.root is None: | ||
| if getattr(value, "root", None) is None: | ||
| value.root = self | ||
| # if it's a closure and there is something in there | ||
| if hasattr(value, "__closure__") and value.__closure__: | ||
| for closure in value.__closure__: | ||
| if isinstance(closure.cell_contents, DynamicClosureCollector): | ||
| closure.cell_contents.peers.add(self) | ||
| contents = get_cell_contents(closure) | ||
| if isinstance(contents, DynamicClosureCollector): | ||
| contents.peers.add(self) | ||
| else: | ||
| ret = lambda: value | ||
| return ret | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,57 @@ | ||
| from numbers import Number | ||
| from types import FunctionType | ||
| try: | ||
| from numbers import Number | ||
| except ImportError: # MicroPython compatibility fallback for missing numbers | ||
| Number = (int, float, complex) # type: ignore[assignment,misc] | ||
|
|
||
| try: | ||
| import inspect | ||
| except ImportError: # MicroPython compatibility fallback for missing inspect | ||
| inspect = None # type: ignore[assignment] | ||
|
|
||
| try: | ||
| from typing import Any | ||
| except ImportError: # MicroPython compatibility fallback for missing typing | ||
| Any = object # type: ignore[assignment,misc] | ||
|
|
||
| try: | ||
| from types import FunctionType | ||
| except ImportError: # MicroPython compatibility fallback for missing types | ||
| FunctionType = type(lambda: None) # type: ignore[assignment,misc] | ||
|
|
||
|
|
||
| if inspect is not None: | ||
|
||
|
|
||
| def is_zero_arg(value: Any) -> bool: | ||
| if not callable(value): | ||
| return False | ||
| return len(inspect.signature(value).parameters) == 0 | ||
|
|
||
| def get_cell_contents(cell: Any) -> Any: | ||
| return cell.cell_contents | ||
|
|
||
| def set_cell_contents(cell: Any, value: Any) -> None: | ||
| cell.cell_contents = value | ||
| else: | ||
|
|
||
| def is_zero_arg(value: Any) -> bool: | ||
| if not callable(value): | ||
| return False | ||
| try: | ||
| return value.__code__.co_argcount == 0 | ||
| except AttributeError: | ||
| return True | ||
|
|
||
| def get_cell_contents(cell: Any) -> Any: | ||
| try: | ||
| return cell.cell_contents | ||
| except AttributeError: | ||
| return cell | ||
|
|
||
| def set_cell_contents(cell: Any, value: Any) -> None: | ||
| try: | ||
| cell.cell_contents = value | ||
| except AttributeError: | ||
| pass # Read-only fallback in MicroPython | ||
|
|
||
|
|
||
| class ClosureCollectorException(AttributeError): | ||
|
|
@@ -9,8 +61,8 @@ class ClosureCollectorException(AttributeError): | |
| def rebind(callable, from_obj, to_obj): | ||
| if getattr(callable, "__closure__", False): | ||
| for cell in callable.__closure__: | ||
| if cell.cell_contents is from_obj: | ||
| cell.cell_contents = to_obj | ||
| if get_cell_contents(cell) is from_obj: | ||
| set_cell_contents(cell, to_obj) | ||
|
|
||
|
|
||
| def is_rule(func): | ||
|
|
@@ -19,7 +71,8 @@ def is_rule(func): | |
|
|
||
| if getattr(func, "__closure__", False): ## TODO replace with inspect_getclosurevars, probably inspect only nonlocals | ||
| for cell in func.__closure__: | ||
| if not isinstance(cell.cell_contents, (str, Number, bytes, tuple, frozenset)): | ||
| contents = get_cell_contents(cell) | ||
| if not isinstance(contents, (str, Number, bytes, tuple, frozenset)): | ||
| return True | ||
| try: | ||
| if set(func.__globals__).intersection(func.__code__.co_names): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jules are there cases where only some of these are available or can we combine all of these into one try/except?