From 5efa8214783dc11d82d3c46a9e7268d50095771a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sat, 9 Nov 2019 17:11:17 +0000 Subject: [PATCH 1/2] Fix regression where using didnt set bound_args correctly --- tests/test_fixtures.py | 32 ++++++++++++++++++++++++++------ tests/test_util.py | 5 ++++- ward/fixtures.py | 1 - ward/models.py | 4 ++-- ward/testing.py | 29 ++++++++++++++++++++--------- 5 files changed, 52 insertions(+), 19 deletions(-) diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index 229c6923..dd8e7a07 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -2,7 +2,7 @@ from tests.test_suite import testable_test from ward import expect, fixture, test, Scope -from ward.fixtures import Fixture, FixtureCache +from ward.fixtures import Fixture, FixtureCache, using from ward.testing import Test @@ -126,11 +126,11 @@ def _( @test("FixtureCache.teardown_fixtures_for_scope removes Test fixtures from cache") def _( cache: FixtureCache = cache, - test: Test = my_test, + t: Test = my_test, ): - cache.teardown_fixtures_for_scope(Scope.Test, test.id) + cache.teardown_fixtures_for_scope(Scope.Test, t.id) - fixtures_at_scope = cache.get_fixtures_at_scope(Scope.Test, test.id) + fixtures_at_scope = cache.get_fixtures_at_scope(Scope.Test, t.id) expect(fixtures_at_scope).equals({}) @@ -138,10 +138,10 @@ def _( @test("FixtureCache.teardown_fixtures_for_scope runs teardown for Test fixtures") def _( cache: FixtureCache = cache, - test: Test = my_test, + t: Test = my_test, events: List = recorded_events, ): - cache.teardown_fixtures_for_scope(Scope.Test, test.id) + cache.teardown_fixtures_for_scope(Scope.Test, t.id) expect(events).equals(["teardown t"]) @@ -186,3 +186,23 @@ def _( cache.teardown_global_fixtures() expect(events).equals(["teardown g"]) + + +@test("using decorator sets bound args correctly") +def _(): + @fixture + def fixture_a(): + pass + + @testable_test + @using( + a=fixture_a, + b="val", + ) + def t(a, b): + pass + + bound_args = t.ward_meta.bound_args + expected = {"a": fixture_a, "b": "val"} + + expect(bound_args.arguments).equals(expected) diff --git a/tests/test_util.py b/tests/test_util.py index fe800616..597e1926 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,5 +1,5 @@ from tests.test_suite import example_test -from ward import expect, test +from ward import expect, test, using from ward.testing import TestOutcome, TestResult from ward.util import ExitCode, get_exit_code @@ -7,6 +7,9 @@ @test( "get_exit_code returns ExitCode.SUCCESS when PASS, SKIP and XFAIL in test results" ) +@using( + example=example_test, +) def _(example=example_test): test_results = [ TestResult(test=example, outcome=TestOutcome.PASS), diff --git a/ward/fixtures.py b/ward/fixtures.py index e1d8ff94..e891dff8 100644 --- a/ward/fixtures.py +++ b/ward/fixtures.py @@ -144,7 +144,6 @@ def wrapper(*args, **kwargs): def using(*using_args, **using_kwargs): def decorator_using(func): - signature = inspect.signature(func) bound_args = signature.bind_partial(*using_args, **using_kwargs) if hasattr(func, "ward_meta"): diff --git a/ward/models.py b/ward/models.py index 9957c633..04f7ce8f 100644 --- a/ward/models.py +++ b/ward/models.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from inspect import Signature +from inspect import BoundArguments from typing import Optional from ward.errors import FixtureError @@ -42,5 +42,5 @@ class WardMeta: description: Optional[str] = None is_fixture: bool = False scope: Scope = Scope.Test - bound_args: Optional[Signature] = None + bound_args: Optional[BoundArguments] = None path: Optional[str] = None diff --git a/ward/testing.py b/ward/testing.py index f29ab4d2..4b4ed3df 100644 --- a/ward/testing.py +++ b/ward/testing.py @@ -211,13 +211,26 @@ def get_result(self, outcome, exception=None): ) return result - def _get_default_args(self) -> Dict[str, Any]: + def _get_default_args(self, func: Optional[Union[Callable, Fixture]] = None) -> Dict[str, Any]: """ - Returns a mapping of test argument names to values. This method does no - fixture resolution. If a value is a fixture function, then the raw fixture - function is used, *not* the `Fixture` object. + Returns a mapping of test argument names to values. + + This method does no fixture resolution. + + If a value is a fixture function, then the raw fixture + function is returned as a value in the dict, *not* the `Fixture` object. """ - signature = inspect.signature(self.fn) + fn = func or self.fn + meta = getattr(fn, "ward_meta", None) + signature = inspect.signature(fn) + + # Override the signature if @using is present + if meta: + bound_args = getattr(fn.ward_meta, "bound_args", None) + if bound_args: + bound_args.apply_defaults() + return bound_args.arguments + default_binding = signature.bind_partial() default_binding.apply_defaults() return default_binding.arguments @@ -271,11 +284,9 @@ def _resolve_single_arg( cache.cache_fixture(fixture, scope_key) return fixture - signature = inspect.signature(arg) - children_defaults = signature.bind_partial() - children_defaults.apply_defaults() + children_defaults = self._get_default_args(func=arg) children_resolved = {} - for name, child_fixture in children_defaults.arguments.items(): + for name, child_fixture in children_defaults.items(): child_resolved = self._resolve_single_arg(child_fixture, cache) children_resolved[name] = child_resolved From 4221c7e0b62d1e12f133e5a24e801802febc6c69 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Sat, 9 Nov 2019 17:11:33 +0000 Subject: [PATCH 2/2] Prepare 0.17.1a0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 63bf2a7b..27f2e466 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup -version = "0.17.0a0" +version = "0.17.1a0" description = "A modern Python 3 test framework for finding and fixing flaws faster." with open("README.md", "r") as fh: if platform.system() != "Windows":