andygrove commented on code in PR #5222:
URL: https://github.com/apache/datafusion-comet/pull/5222#discussion_r3713598860


##########
spark/src/test/scala/org/apache/comet/iceberg/IcebergReflectionSuite.scala:
##########
@@ -63,4 +63,90 @@ class IcebergReflectionSuite extends AnyFunSuite {
     assert(metadata.isDefined)
     assert(metadata.get.isInstanceOf[TableMetadata])
   }
+
+  test("findMethod resolves a method once and returns the cached instance") {
+    val first = IcebergReflection.findMethod(classOf[Schema], "columns")
+    val second = IcebergReflection.findMethod(classOf[Schema], "columns")
+    assert(first.isDefined)
+    assert(first.get.getName == "columns")
+    // Class.getMethod hands back a fresh copy per call; the cache must not.
+    assert(first.get eq second.get)
+  }
+
+  test("an absent method is a cached miss, and getMethod still throws for it") 
{
+    assert(IcebergReflection.findMethod(classOf[Schema], 
"noSuchAccessor").isEmpty)
+    assert(IcebergReflection.findMethod(classOf[Schema], 
"noSuchAccessor").isEmpty)
+    assertThrows[NoSuchMethodException] {
+      IcebergReflection.getMethod(classOf[Schema], "noSuchAccessor")
+    }
+  }
+
+  test("findMethod distinguishes overloads by parameter type") {
+    val byId = IcebergReflection.findMethod(classOf[Schema], "findField", 
classOf[Int])
+    val byName = IcebergReflection.findMethod(classOf[Schema], "findField", 
classOf[String])
+    assert(byId.isDefined && byName.isDefined)
+    assert(byId.get ne byName.get)
+
+    val schema = new Schema(Types.NestedField.required(7, "id", 
Types.IntegerType.get()))
+    val fieldById = byId.get.invoke(schema, 
Integer.valueOf(7)).asInstanceOf[Types.NestedField]
+    val fieldByName = byName.get.invoke(schema, 
"id").asInstanceOf[Types.NestedField]
+    assert(fieldById.name() == "id")
+    assert(fieldByName.fieldId() == 7)
+  }
+
+  test("findMethodInHierarchy finds an inherited method and caches it") {
+    val first = 
IcebergReflection.findMethodInHierarchy(classOf[StubTableOperations], "current")
+    val second = 
IcebergReflection.findMethodInHierarchy(classOf[StubTableOperations], "current")
+    assert(first.isDefined)
+    // current() is declared on BaseMetastoreTableOperations, not on the stub 
itself.
+    assert(first.get.getDeclaringClass == 
classOf[BaseMetastoreTableOperations])
+    assert(first.get eq second.get)
+    
assert(IcebergReflection.findMethodInHierarchy(classOf[StubTableOperations], 
"nope").isEmpty)
+  }
+
+  test("extractFileLocation reads location() when the class has one") {
+    val file = new LocationFile("s3://bucket/data/f.parquet")
+    assert(
+      IcebergReflection.extractFileLocation(classOf[LocationFile], file) ==
+        Some("s3://bucket/data/f.parquet"))
+  }
+
+  test("extractFileLocation falls back to path() on Iceberg versions without 
location()") {
+    val file = new PathOnlyFile("s3://bucket/data/f.parquet")
+    // Called twice: the second call reads the cached "location() is absent" 
answer.
+    assert(
+      IcebergReflection.extractFileLocation(classOf[PathOnlyFile], file) ==
+        Some("s3://bucket/data/f.parquet"))
+    assert(
+      IcebergReflection.extractFileLocation(classOf[PathOnlyFile], file) ==
+        Some("s3://bucket/data/f.parquet"))
+  }
+
+  test("extractFileLocation returns None when the class exposes neither 
accessor") {
+    assert(IcebergReflection.extractFileLocation(classOf[Object], new 
Object).isEmpty)
+  }
+
+  test("a resolved method has access checks suppressed") {
+    // Iceberg's bound terms and file impls are instances of package-private 
classes, so the
+    // accessors Comet resolves on them have to be accessible before they can 
be invoked.
+    val method = IcebergReflection.findMethod(classOf[HiddenTransform], 
"transform")
+    assert(method.isDefined)
+    assert(method.get.isAccessible)
+    assert(method.get.invoke(new HiddenTransform).toString == "identity")
+  }

Review Comment:
   Good catch on the flag-vs-behavior distinction: the `invoke` on the next 
line proved nothing, since `HiddenTransform` compiles to a public class.
   
   `private[iceberg]` wouldn't get there either, for two reasons. scalac emits 
qualified-private (and plain `private`) nested classes as *public* JVM classes, 
since the JVM has no equivalent of qualified private. And even with 
package-private bytecode, the caller here is 
`org.apache.comet.iceberg.IcebergReflectionSuite`, which is in the same runtime 
package as the stub, so the access check would pass anyway.
   
   Fixed in ba95bdf9e by using a real Iceberg file instead of a synthetic 
class: `DataFiles.builder(...).build()` returns a `GenericDataFile`, whose 
accessors are declared on `BaseFile`, and both are package-private in 1.5.2 
(Spark 3.4), 1.8.1 (3.5) and 1.11.0 (4.1). Invoking `path()` on one from 
Comet's package is exactly the situation `makeAccessible` exists for. 
`Modifier.isPublic` assertions on the concrete and declaring classes keep the 
test from going vacuous if Iceberg ever makes them public.
   
   Verified by stubbing `makeAccessible` to a no-op:
   
   ```
   - a resolved method has access checks suppressed *** FAILED ***
     java.lang.IllegalAccessException: class 
org.apache.comet.iceberg.IcebergReflectionSuite
     cannot access a member of class org.apache.iceberg.BaseFile with modifiers 
"public"
   ```
   
   and 9/9 passing with it restored.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to