Copilot commented on code in PR #5698: URL: https://github.com/apache/texera/pull/5698#discussion_r3408753199
########## common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/source/sql/postgresql/PostgreSQLConnUtilSpec.scala: ########## @@ -0,0 +1,226 @@ +/* + * 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. + */ + +package org.apache.texera.amber.operator.source.sql.postgresql + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.BeforeAndAfterAll + +import java.lang.reflect.{InvocationHandler, Method, Proxy} +import java.sql.{Connection, Driver, DriverManager, DriverPropertyInfo, SQLException} +import java.util.Properties +import java.util.logging.Logger +import scala.collection.mutable.ArrayBuffer +import scala.jdk.CollectionConverters._ + +class PostgreSQLConnUtilSpec extends AnyFlatSpec with BeforeAndAfterAll { + + // --------------------------------------------------------------------------- + // Strategy — pin the JDBC URL composition (the only application-logic in + // this util) without a real DB. + // + // The workflow-operator test classpath DOES include the real PostgreSQL + // driver (transitively), and that driver eats `jdbc:postgresql:` URLs + // before returning a generic "The connection attempt failed." exception. + // So we can't rely on `DriverManager.getConnection`'s default + // "No suitable driver" message. + // + // Instead, we deregister every driver claiming `jdbc:postgresql:`, + // register a capturing driver that records each URL it is asked to open + // (and returns a Proxy-backed Connection so the production code can call + // `setReadOnly`), run the assertions, then restore the real drivers + // in afterAll. + // --------------------------------------------------------------------------- + + private object CapturingPGDriver extends Driver { + val seenUrls: ArrayBuffer[String] = ArrayBuffer.empty + val seenProps: ArrayBuffer[Properties] = ArrayBuffer.empty + val readOnlyCalls: ArrayBuffer[Boolean] = ArrayBuffer.empty + + override def connect(url: String, info: Properties): Connection = { + if (!acceptsURL(url)) return null + seenUrls += url + seenProps += info + Proxy + .newProxyInstance( + getClass.getClassLoader, + Array(classOf[Connection]), + new InvocationHandler { + override def invoke(p: Any, m: Method, args: Array[AnyRef]): AnyRef = + m.getName match { + case "setReadOnly" => + readOnlyCalls += args(0).asInstanceOf[java.lang.Boolean].booleanValue() + null + // Object methods — required so `conn != null`, `conn.toString`, + // and identity HashMap-keying work without NPE on auto-unboxing. + case "equals" => java.lang.Boolean.valueOf(p eq args(0)) + case "hashCode" => java.lang.Integer.valueOf(System.identityHashCode(p)) + case "toString" => "CapturingPGDriver.StubConnection@" + System.identityHashCode(p) + case "isWrapperFor" => java.lang.Boolean.FALSE + case "close" => null + case _ => null + } + } + ) + .asInstanceOf[Connection] + } + override def acceptsURL(url: String): Boolean = + url != null && url.startsWith("jdbc:postgresql:") + override def getPropertyInfo(url: String, info: Properties): Array[DriverPropertyInfo] = + Array.empty + override def getMajorVersion: Int = 1 + override def getMinorVersion: Int = 0 + override def jdbcCompliant(): Boolean = false + override def getParentLogger: Logger = Logger.getLogger("test-pg-capturing") + } + + // Snapshot the real PG drivers so afterAll can restore them. + private val savedRealDrivers: List[Driver] = ArrayBuffer.empty[Driver].toList + + override protected def beforeAll(): Unit = { + super.beforeAll() + // Remove every other driver that accepts jdbc:postgresql: so our + // capturing driver is the only one DriverManager.getConnection sees. + val others = DriverManager.getDrivers.asScala.toList.filter { d => + d != CapturingPGDriver && d.acceptsURL("jdbc:postgresql://h/d") + } + others.foreach { d => + savedRealDriversBuffer += d + DriverManager.deregisterDriver(d) + } + DriverManager.registerDriver(CapturingPGDriver) + } Review Comment: `Driver.acceptsURL` is allowed to throw `SQLException`. Calling it directly during `beforeAll` can make the suite fail during setup (and potentially leave JDBC drivers deregistered), causing cascading failures in other tests. Wrap the `acceptsURL` probe defensively and add best-effort restoration if setup fails after deregistering drivers. ########## common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/source/sql/mysql/MySQLConnUtilSpec.scala: ########## @@ -0,0 +1,119 @@ +/* + * 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. + */ + +package org.apache.texera.amber.operator.source.sql.mysql + +import org.scalatest.flatspec.AnyFlatSpec + +import java.sql.SQLException + +class MySQLConnUtilSpec extends AnyFlatSpec { + + // --------------------------------------------------------------------------- + // Strategy — same approach as PostgreSQLConnUtilSpec. The workflow-operator + // test classpath does not carry a MySQL driver, so DriverManager.getConnection + // throws SQLException("No suitable driver found for " + url). The URL is + // present in the exception message, which is what we pin on. + // --------------------------------------------------------------------------- Review Comment: `MySQLConnUtil.connect` also calls `connection.setReadOnly(true)` (application-controlled behavior), but this spec never asserts that the returned `Connection` is flipped to read-only (unlike `PostgreSQLConnUtilSpec`). Consider registering a lightweight capturing `jdbc:mysql:` `Driver` (similar to the PG spec) that returns a Proxy `Connection`, so you can assert the `setReadOnly(true)` call without requiring a real DB or a MySQL driver on the test classpath. ########## common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/source/sql/postgresql/PostgreSQLConnUtilSpec.scala: ########## @@ -0,0 +1,226 @@ +/* + * 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. + */ + +package org.apache.texera.amber.operator.source.sql.postgresql + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.BeforeAndAfterAll + +import java.lang.reflect.{InvocationHandler, Method, Proxy} +import java.sql.{Connection, Driver, DriverManager, DriverPropertyInfo, SQLException} +import java.util.Properties +import java.util.logging.Logger +import scala.collection.mutable.ArrayBuffer +import scala.jdk.CollectionConverters._ + +class PostgreSQLConnUtilSpec extends AnyFlatSpec with BeforeAndAfterAll { + + // --------------------------------------------------------------------------- + // Strategy — pin the JDBC URL composition (the only application-logic in + // this util) without a real DB. + // + // The workflow-operator test classpath DOES include the real PostgreSQL + // driver (transitively), and that driver eats `jdbc:postgresql:` URLs + // before returning a generic "The connection attempt failed." exception. + // So we can't rely on `DriverManager.getConnection`'s default + // "No suitable driver" message. + // + // Instead, we deregister every driver claiming `jdbc:postgresql:`, + // register a capturing driver that records each URL it is asked to open + // (and returns a Proxy-backed Connection so the production code can call + // `setReadOnly`), run the assertions, then restore the real drivers + // in afterAll. + // --------------------------------------------------------------------------- + + private object CapturingPGDriver extends Driver { + val seenUrls: ArrayBuffer[String] = ArrayBuffer.empty + val seenProps: ArrayBuffer[Properties] = ArrayBuffer.empty + val readOnlyCalls: ArrayBuffer[Boolean] = ArrayBuffer.empty + + override def connect(url: String, info: Properties): Connection = { + if (!acceptsURL(url)) return null + seenUrls += url + seenProps += info + Proxy + .newProxyInstance( + getClass.getClassLoader, + Array(classOf[Connection]), + new InvocationHandler { + override def invoke(p: Any, m: Method, args: Array[AnyRef]): AnyRef = + m.getName match { + case "setReadOnly" => + readOnlyCalls += args(0).asInstanceOf[java.lang.Boolean].booleanValue() + null + // Object methods — required so `conn != null`, `conn.toString`, + // and identity HashMap-keying work without NPE on auto-unboxing. + case "equals" => java.lang.Boolean.valueOf(p eq args(0)) + case "hashCode" => java.lang.Integer.valueOf(System.identityHashCode(p)) + case "toString" => "CapturingPGDriver.StubConnection@" + System.identityHashCode(p) + case "isWrapperFor" => java.lang.Boolean.FALSE + case "close" => null + case _ => null + } + } + ) + .asInstanceOf[Connection] + } + override def acceptsURL(url: String): Boolean = + url != null && url.startsWith("jdbc:postgresql:") + override def getPropertyInfo(url: String, info: Properties): Array[DriverPropertyInfo] = + Array.empty + override def getMajorVersion: Int = 1 + override def getMinorVersion: Int = 0 + override def jdbcCompliant(): Boolean = false + override def getParentLogger: Logger = Logger.getLogger("test-pg-capturing") + } + + // Snapshot the real PG drivers so afterAll can restore them. + private val savedRealDrivers: List[Driver] = ArrayBuffer.empty[Driver].toList + Review Comment: `savedRealDrivers` is initialized to an empty list and never used; the actual restore logic uses `savedRealDriversBuffer`. This is misleading (especially given the preceding comment) and should be removed or wired up to the real buffer used for restoration. -- 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]
