Commit ff9dc580 authored by Xavier Barbosa's avatar Xavier Barbosa

not class for a start

parent d2fc8fb2
# encoding: rspec
from rspec import context, describe, it
def dec(*opts):
def wrapper(func):
def wrapped(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
with describe("I have 42 foo"):
with context("when foo=42"):
......@@ -32,7 +37,7 @@ class ThatTest:
def this(self):
assert True
@dec(a, d)
@dec("a", "b")
def test_that(self):
with context("when True"):
this = True
......
......@@ -48,32 +48,54 @@ def is_test_async_func(node):
def is_test_cls(node):
return False
if isinstance(node, ast.ClassDef):
if node.name.endswith("Test"):
if node.name.startswith("Test"):
return True
return False
class Bundle(list):
def __init__(self):
self.marker = []
self.markers = []
@property
def decorators(self):
results = []
for marker in self.markers:
for item in marker.node.items:
results.append(item.context_expr)
return results
@classmethod
def create(cls, items):
if isinstance(items, cls):
return items
instance = cls()
instance.extend(items)
return instance
def child(self, of=None):
instance = Bundle()
instance.markers = self.markers[:]
instance.of = of
if of:
instance.markers.append(of)
return instance
def flatten_node(items):
markers = {item for item in items if isinstance(item, Marker)}
if not markers:
yield items
items = Bundle.create(items)
bundles = [items.child(item)
for i, item in enumerate(items)
if isinstance(item, Marker)]
if not bundles:
yield items, items.decorators
return
first = True
while first or markers:
first = False
bundle = Bundle()
marked = None
for bundle in bundles:
for item in items:
if isinstance(item, Marker):
if not marked and item in markers:
markers.discard(item)
marked = True
if bundle.of is item:
bundle.extend(item.body)
elif isinstance(item, ast.AST):
bundle.append(item)
......@@ -83,10 +105,12 @@ def flatten_node(items):
def flatten_node_2(items):
bundles = list(flatten_node(items))
if len(bundles) == 1:
yield "", bundles[0]
body, decorators = bundles[0]
yield "", body, decorators
else:
for i, body in enumerate(bundles):
yield f"_RSPEC_{i}", body
for i, bundle in enumerate(bundles):
body, decorators = bundle
yield f"_RSPEC_{i}", body, decorators
class Marker:
......@@ -130,7 +154,7 @@ class Visitor(ast.NodeTransformer):
def visit_rspec_describe(self, node: ast.AST):
decorators = [item.context_expr for item in node.items]
items = self.visit_marker(node).body
for suffix, body in flatten_node_2(items):
for suffix, body, decorators in flatten_node_2(items):
yield ast.FunctionDef(name=f"test_describe{suffix}",
args=[],
body=body,
......@@ -138,22 +162,23 @@ class Visitor(ast.NodeTransformer):
def visit_test_func(self, node: ast.FunctionDef):
items = self.visit_marker(node).body
for suffix, body in flatten_node_2(items):
node = copy(node)
node.name = f"{node.name}{suffix}"
node.body = body
yield node
for suffix, body, decorators in flatten_node_2(items):
instance = copy(node)
instance.name = f"{node.name}{suffix}"
instance.body = body
instance.decorator_list = node.decorator_list + decorators
yield instance
def visit_test_async_func(self, node: ast.AsyncFunctionDef):
items = self.visit_marker(node).body
for suffix, body in flatten_node_2(items):
for suffix, body, decorators in flatten_node_2(items):
instance = copy(node)
instance.name = f"{node.name}{suffix}"
instance.body = body
instance.decorator_list = node.decorator_list + decorators
yield instance
def visit_test_cls(self, node: ast.ClassDef):
instance = copy(node)
items: List[ast.stmt] = []
for element in node.body:
if is_test_func(element):
......@@ -164,6 +189,7 @@ class Visitor(ast.NodeTransformer):
items.extend(self.visit_test_async_func(element))
else:
items.append(element)
instance = copy(node)
instance.body = items
yield instance
......@@ -189,137 +215,3 @@ class Visitor(ast.NodeTransformer):
def run(self, node):
return self.visit(node)
"""
-- ASDL's 5 builtin types are:
-- identifier, int, string, object, constant
module Python
{
mod = Module(stmt* body, type_ignore *type_ignores)
| Interactive(stmt* body)
| Expression(expr body)
| FunctionType(expr* argtypes, expr returns)
-- not really an actual node but useful in Jython's typesystem.
| Suite(stmt* body)
stmt = FunctionDef(identifier name, arguments args,
stmt* body, expr* decorator_list, expr? returns,
string? type_comment)
| AsyncFunctionDef(identifier name, arguments args,
stmt* body, expr* decorator_list, expr? returns,
string? type_comment)
| ClassDef(identifier name,
expr* bases,
keyword* keywords,
stmt* body,
expr* decorator_list)
| Return(expr? value)
| Delete(expr* targets)
| Assign(expr* targets, expr value, string? type_comment)
| AugAssign(expr target, operator op, expr value)
-- 'simple' indicates that we annotate simple name without parens
| AnnAssign(expr target, expr annotation, expr? value, int simple)
-- use 'orelse' because else is a keyword in target languages
| For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)
| AsyncFor(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)
| While(expr test, stmt* body, stmt* orelse)
| If(expr test, stmt* body, stmt* orelse)
| With(withitem* items, stmt* body, string? type_comment)
| AsyncWith(withitem* items, stmt* body, string? type_comment)
| Raise(expr? exc, expr? cause)
| Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)
| Assert(expr test, expr? msg)
| Import(alias* names)
| ImportFrom(identifier? module, alias* names, int? level)
| Global(identifier* names)
| Nonlocal(identifier* names)
| Expr(expr value)
| Pass | Break | Continue
-- XXX Jython will be different
-- col_offset is the byte offset in the utf8 string the parser uses
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
-- BoolOp() can use left & right?
expr = BoolOp(boolop op, expr* values)
| NamedExpr(expr target, expr value)
| BinOp(expr left, operator op, expr right)
| UnaryOp(unaryop op, expr operand)
| Lambda(arguments args, expr body)
| IfExp(expr test, expr body, expr orelse)
| Dict(expr* keys, expr* values)
| Set(expr* elts)
| ListComp(expr elt, comprehension* generators)
| SetComp(expr elt, comprehension* generators)
| DictComp(expr key, expr value, comprehension* generators)
| GeneratorExp(expr elt, comprehension* generators)
-- the grammar constrains where yield expressions can occur
| Await(expr value)
| Yield(expr? value)
| YieldFrom(expr value)
-- need sequences for compare to distinguish between
-- x < 4 < 3 and (x < 4) < 3
| Compare(expr left, cmpop* ops, expr* comparators)
| Call(expr func, expr* args, keyword* keywords)
| FormattedValue(expr value, int? conversion, expr? format_spec)
| JoinedStr(expr* values)
| Constant(constant value, string? kind)
-- the following expression can appear in assignment context
| Attribute(expr value, identifier attr, expr_context ctx)
| Subscript(expr value, slice slice, expr_context ctx)
| Starred(expr value, expr_context ctx)
| Name(identifier id, expr_context ctx)
| List(expr* elts, expr_context ctx)
| Tuple(expr* elts, expr_context ctx)
-- col_offset is the byte offset in the utf8 string the parser uses
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
expr_context = Load | Store | Del | AugLoad | AugStore | Param
slice = Slice(expr? lower, expr? upper, expr? step)
| ExtSlice(slice* dims)
| Index(expr value)
boolop = And | Or
operator = Add | Sub | Mult | MatMult | Div | Mod | Pow | LShift
| RShift | BitOr | BitXor | BitAnd | FloorDiv
unaryop = Invert | Not | UAdd | USub
cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn
comprehension = (expr target, expr iter, expr* ifs, int is_async)
excepthandler = ExceptHandler(expr? type, identifier? name, stmt* body)
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
arguments = (arg* posonlyargs, arg* args, arg? vararg, arg* kwonlyargs,
expr* kw_defaults, arg? kwarg, expr* defaults)
arg = (identifier arg, expr? annotation, string? type_comment)
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
-- keyword arguments supplied to call (NULL identifier for **kwargs)
keyword = (identifier? arg, expr value)
-- import name with optional 'as' alias.
alias = (identifier name, identifier? asname)
withitem = (expr context_expr, expr? optional_vars)
type_ignore = TypeIgnore(int lineno, string tag)
}
"""
import importlib.abc
import importlib.util
import pathlib
import pytest
from py._path.local import LocalPath
......@@ -10,10 +11,19 @@ from rspec.parser import transform_from_bytes
class RSpecLoader(importlib.abc.FileLoader, importlib.abc.SourceLoader):
def get_data(self, path):
data = super().get_data(path)
data = transform_from_bytes(data)
if path.endswith(".py"):
data = transform_from_bytes(data)
p = pathlib.Path(path[:-3])
with (p.parent / "__pycache__" / (p.name + ".pytest.py")).open("wb") as file:
file.write(data)
return data
def real_file_source(self, path):
if path.endswith(".py"):
p = pathlib.Path(path[:-3])
return str(p.parent / "__pycache__" / (p.name + ".pytest.py"))
return path
class Path(LocalPath):
def __init__(self, strpath):
......@@ -32,9 +42,6 @@ class Path(LocalPath):
return mod
def pyimport(self, modname=None, ensuresyspath=True):
if not self.check():
raise py.error.ENOENT(self)
try:
modname = modname or self.purebasename
return self._import_rspec(modname)
......@@ -48,3 +55,18 @@ def pytest_pycollect_makemodule(path, parent):
if path.basename == "__init__.py":
return pytest.Package(path, parent)
return pytest.Module(path, parent)
@pytest.hookimpl(tryfirst=True)
def pytest_pycollect_makeitem(collector, name, obj):
if collector.istestclass(obj, name):
return pytest.Class(name, parent=collector)
elif collector.istestfunction(obj, name):
tests = []
for test in collector._genfunctions(name, obj):
if '_RSPEC_' in test.name:
a, _, b = test.name.partition("_RSPEC_")
test.name = f"{a}[{b}]"
test.originalname = a
tests.append(test)
return tests
......@@ -14,3 +14,11 @@ with describe("I have 42 foo"):
assert True
with it("fails"):
assert False
class TestFoo:
def test_yo(self):
with it("fails"):
assert False
with it("works"):
assert False
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment