This is an automated email from the ASF dual-hosted git repository.
aglinxinyuan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new d65745b8fa test(pyamber): add unit tests for customized_queue
inner-class decorators (#4782)
d65745b8fa is described below
commit d65745b8fa0237cfd529f9dcff37719b096ec077
Author: Yicong Huang <[email protected]>
AuthorDate: Sun May 3 08:40:31 2026 -0700
test(pyamber): add unit tests for customized_queue inner-class decorators
(#4782)
### What changes were proposed in this PR?
Adds pytest coverage for
`amber/src/main/python/core/util/customized_queue/inner.py`. The
vendored `@inner` / `@static_inner` / `@class_inner` decorator family
used by `LinkedBlockingMultiQueue` had no dedicated spec.
### Any related issues, documentation, discussions?
Closes #4779.
### How was this PR tested?
```
cd amber/src/main/python
ruff check core/util/customized_queue/test_inner.py
ruff format --check core/util/customized_queue/test_inner.py
python -m pytest core/util/customized_queue/test_inner.py
```
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (claude-opus-4-7)
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
.../core/util/customized_queue/test_inner.py | 182 +++++++++++++++++++++
1 file changed, 182 insertions(+)
diff --git a/amber/src/main/python/core/util/customized_queue/test_inner.py
b/amber/src/main/python/core/util/customized_queue/test_inner.py
new file mode 100644
index 0000000000..e443a7063d
--- /dev/null
+++ b/amber/src/main/python/core/util/customized_queue/test_inner.py
@@ -0,0 +1,182 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+
+from core.util.customized_queue.inner import (
+ class_inner,
+ inner,
+ raw_inner,
+ static_inner,
+)
+
+
+class TestRawInner:
+ def test_returns_the_class_unchanged(self):
+ # raw_inner is a no-op decorator preserved for forward-compat with
+ # potential changes to default Python inner-class semantics.
+ class C:
+ pass
+
+ assert raw_inner(C) is C
+
+
+class TestStaticInner:
+ def test_assigns_owner_to_outer_class_at_definition_time(self):
+ class Outer:
+ @static_inner
+ class Inner:
+ pass
+
+ # __set_name__ replaces the descriptor with the actual inner class
+ # and stamps `owner` so the inner can refer back to its outer class.
+ assert Outer.Inner.owner is Outer
+
+ def test_inner_class_is_accessible_directly_on_outer(self):
+ class Outer:
+ @static_inner
+ class Inner:
+ @staticmethod
+ def hello():
+ return "hi"
+
+ assert Outer.Inner.hello() == "hi"
+
+
+class TestClassInnerDescriptorGuard:
+ @pytest.mark.parametrize("descriptor_method", ["__get__", "__set__",
"__del__"])
+ def test_rejects_classes_that_implement_descriptor_methods(self,
descriptor_method):
+ # class_inner refuses to wrap classes that look like descriptors —
+ # the `__get__` it installs would conflict with the user-provided one.
+ attrs = {descriptor_method: lambda *args, **kwargs: None}
+ bad_cls = type("Bad", (object,), attrs)
+ with pytest.raises(ValueError, match="descriptors"):
+ class_inner(bad_cls)
+
+
+class TestClassInnerCarriedInheritance:
+ def test_subclass_of_outer_gets_a_derived_inner_class(self):
+ # When the outer class is subclassed, accessing its inner-class
+ # attribute on the subclass produces a *new* inner class that lists
+ # the parent's inner class among its bases — this is "carried
+ # inheritance". The two inner classes are not the same object and
+ # the derived one's owner points at the subclass.
+ class Outer:
+ @class_inner
+ class Inner:
+ pass
+
+ class Sub(Outer):
+ pass
+
+ derived = Sub.Inner
+ assert derived is not Outer.Inner
+ assert Outer.Inner in derived.__mro__
+ assert derived.owner is Sub
+ # Direct access on the original outer is still the original class.
+ assert Outer.Inner.owner is Outer
+
+
+class TestInnerInstanceBinding:
+ def
test_outer_instance_inner_call_binds_owner_to_that_outer_instance(self):
+ # This is the most common @inner usage in Texera: an inner class on
+ # an outer object instance, where the inner needs `self.owner` to
+ # reach the outer object (e.g. LinkedBlockingMultiQueue's Node /
+ # SubQueue / PriorityGroup).
+ class Outer:
+ def __init__(self, label):
+ self.label = label
+
+ @inner
+ class Inner:
+ def __init__(self, x):
+ self.x = x
+
+ a = Outer("a")
+ b = Outer("b")
+ a_inner = a.Inner(7)
+ b_inner = b.Inner(11)
+
+ assert a_inner.x == 7
+ assert b_inner.x == 11
+ assert a_inner.owner is a
+ assert b_inner.owner is b
+ # Instances are independent; binding to one doesn't leak to the other.
+ assert a_inner is not b_inner
+
+
+class TestInnerProperty:
+ def test_property_auto_instantiates_inner_on_access(self):
+ class Outer:
+ @inner.property
+ class Counter:
+ def __init__(self):
+ self.value = 0
+
+ outer = Outer()
+ c = outer.Counter
+ # The property short-circuits the constructor signature so accessing
+ # the attribute returns a configured instance directly.
+ assert c.value == 0
+ assert c.owner is outer
+
+ def test_property_returns_a_new_instance_each_access(self):
+ # Plain `@inner.property` (without `cached_property`) does not memoize,
+ # so two accesses produce two distinct instances. Pin this so the
+ # difference between `property` and `cached_property` is preserved.
+ class Outer:
+ @inner.property
+ class Counter:
+ def __init__(self):
+ self.value = 0
+
+ outer = Outer()
+ first = outer.Counter
+ second = outer.Counter
+ assert first is not second
+
+
+class TestInnerCachedProperty:
+ def test_cached_property_returns_the_same_instance_on_repeat_access(self):
+ # cached_property memoizes the inner instance on the outer object,
+ # so subsequent attribute reads return the exact same object.
+ class Outer:
+ @inner.cached_property
+ class Counter:
+ def __init__(self):
+ self.value = 0
+
+ outer = Outer()
+ first = outer.Counter
+ first.value = 42
+ second = outer.Counter
+ assert first is second
+ assert second.value == 42
+
+ def test_cached_property_caches_independently_per_outer_instance(self):
+ class Outer:
+ @inner.cached_property
+ class Counter:
+ def __init__(self):
+ self.value = 0
+
+ a = Outer()
+ b = Outer()
+ a.Counter.value = 1
+ b.Counter.value = 2
+ assert a.Counter.value == 1
+ assert b.Counter.value == 2