mdedetrich commented on code in PR #12728:
URL: https://github.com/apache/kafka/pull/12728#discussion_r1011807675


##########
connect/runtime/src/test/java/org/apache/kafka/connect/runtime/standalone/StandaloneHerderTest.java:
##########
@@ -123,91 +134,91 @@ private enum SourceSink {
     private final ConnectorClientConfigOverridePolicy
         noneConnectorClientConfigOverridePolicy = new 
NoneConnectorClientConfigOverridePolicy();
 
+    private MockedStatic<Plugins> pluginsStatic;
+
+    private MockedStatic<WorkerConnector> workerConnectorStatic;
 
     @Before
     public void setup() {
-        worker = PowerMock.createMock(Worker.class);
-        String[] methodNames = new String[]{"connectorTypeForClass"/*, 
"validateConnectorConfig"*/, "buildRestartPlan", "recordRestarting"};
-        herder = PowerMock.createPartialMock(StandaloneHerder.class, 
methodNames,
-                worker, WORKER_ID, KAFKA_CLUSTER_ID, statusBackingStore, new 
MemoryConfigBackingStore(transformer), noneConnectorClientConfigOverridePolicy);
+        worker = mock(Worker.class);
+        herder = mock(StandaloneHerder.class, withSettings()
+            .useConstructor(worker, WORKER_ID, KAFKA_CLUSTER_ID, 
statusBackingStore, new MemoryConfigBackingStore(transformer), 
noneConnectorClientConfigOverridePolicy)
+            .defaultAnswer(CALLS_REAL_METHODS));
         createCallback = new FutureCallback<>();
-        plugins = PowerMock.createMock(Plugins.class);
-        pluginLoader = PowerMock.createMock(PluginClassLoader.class);
-        delegatingLoader = PowerMock.createMock(DelegatingClassLoader.class);
-        PowerMock.mockStatic(Plugins.class);
-        PowerMock.mockStatic(WorkerConnector.class);
-        Capture<Map<String, String>> configCapture = Capture.newInstance();
-        EasyMock.expect(transformer.transform(eq(CONNECTOR_NAME), 
EasyMock.capture(configCapture))).andAnswer(configCapture::getValue).anyTimes();
+        plugins = mock(Plugins.class);
+        pluginLoader = mock(PluginClassLoader.class);
+        delegatingLoader = mock(DelegatingClassLoader.class);
+        pluginsStatic = mockStatic(Plugins.class);
+        workerConnectorStatic = mockStatic(WorkerConnector.class);
+        final ArgumentCaptor<Map<String, String>> configCapture = 
ArgumentCaptor.forClass(Map.class);
+        when(transformer.transform(eq(CONNECTOR_NAME), 
configCapture.capture())).thenAnswer(invocation -> configCapture.getValue());
+    }
+
+    @After
+    public void tearDown() {
+        pluginsStatic.close();
+        workerConnectorStatic.close();
     }
 
     @Test
     public void testCreateSourceConnector() throws Exception {
-        connector = PowerMock.createMock(BogusSourceConnector.class);
+        connector = mock(BogusSourceConnector.class);
         expectAdd(SourceSink.SOURCE);
 
         Map<String, String> config = connectorConfig(SourceSink.SOURCE);
-        Connector connectorMock = PowerMock.createMock(SourceConnector.class);
+        Connector connectorMock = mock(SourceConnector.class);
         expectConfigValidation(connectorMock, true, config);
 
-        PowerMock.replayAll();
-
         herder.putConnectorConfig(CONNECTOR_NAME, config, false, 
createCallback);
         Herder.Created<ConnectorInfo> connectorInfo = 
createCallback.get(1000L, TimeUnit.SECONDS);
         assertEquals(createdInfo(SourceSink.SOURCE), connectorInfo.result());
-
-        PowerMock.verifyAll();
     }
 
     @Test
     public void testCreateConnectorFailedValidation() {
         // Basic validation should be performed and return an error, but 
should still evaluate the connector's config
-        connector = PowerMock.createMock(BogusSourceConnector.class);
+        connector = mock(BogusSourceConnector.class);
 
         Map<String, String> config = connectorConfig(SourceSink.SOURCE);
         config.remove(ConnectorConfig.NAME_CONFIG);
 
-        Connector connectorMock = PowerMock.createMock(SourceConnector.class);
-        
EasyMock.expect(worker.configTransformer()).andReturn(transformer).times(2);
-        final Capture<Map<String, String>> configCapture = 
EasyMock.newCapture();
-        
EasyMock.expect(transformer.transform(EasyMock.capture(configCapture))).andAnswer(configCapture::getValue);
-        EasyMock.expect(worker.getPlugins()).andReturn(plugins).times(3);
-        
EasyMock.expect(plugins.compareAndSwapLoaders(connectorMock)).andReturn(delegatingLoader);
-        
EasyMock.expect(plugins.newConnector(EasyMock.anyString())).andReturn(connectorMock);
+        Connector connectorMock = mock(SourceConnector.class);
+        when(worker.configTransformer()).thenReturn(transformer);
+        final ArgumentCaptor<Map<String, String>> configCapture = 
ArgumentCaptor.forClass(Map.class);
+        
when(transformer.transform(configCapture.capture())).thenAnswer(invocation -> 
configCapture.getValue());
+        when(worker.getPlugins()).thenReturn(plugins);
+        
when(plugins.compareAndSwapLoaders(connectorMock)).thenReturn(delegatingLoader);
+        when(plugins.newConnector(anyString())).thenReturn(connectorMock);
 
-        EasyMock.expect(connectorMock.config()).andStubReturn(new ConfigDef());
+        when(connectorMock.config()).thenReturn(new ConfigDef());
 
         ConfigValue validatedValue = new ConfigValue("foo.bar");
-        EasyMock.expect(connectorMock.validate(config)).andReturn(new 
Config(singletonList(validatedValue)));
-        
EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)).andReturn(pluginLoader);
-
-        PowerMock.replayAll();
+        when(connectorMock.validate(config)).thenReturn(new 
Config(singletonList(validatedValue)));
+        
when(Plugins.compareAndSwapLoaders(delegatingLoader)).thenReturn(pluginLoader);

Review Comment:
   > I'm still not sure this is safe to merge as-is, though. The 
runOnHerderConnectorExecutor method is fairly brittle, and may not serve our 
needs in the future if we end up needing to test concurrent connector 
validations. In addition, it does not prevent classloaders from being swapped 
out on other threads, such as the main testing thread or ones created and used 
by the standalone herder's requestExecutorService.
   
   Agreed, I do believe the reason why the test happens to work right now is 
that the current default `CachedThreadPool` happens to always use a single 
thread due to the fact that the tests and validations are run in serial. I am 
not sure of the exact details behind why Mockito's mockstatic only works for 
the thread where the mock was made, my initial assumption is that its due to 
using `ThreadLocal` which if is the case then the `CachedThreadPool` may have 
theoretically worked even for concurrent tests/validations due to them sharing 
the same `ThreadLocal` (which iirc is the default implementation behind 
`CachedThreadPool`).
   
   I do definitely agree that its not a nice a solution, the user experience 
for people contributing tests is not ideal since they always have to remember 
to run static mocks on the `connectorExecutor` and as we have seen in the 
progress of the PR, if you forget to do this the tests can still inadvertently 
pass. 
   
   I don't have the time right at this moment to work on such a feature but 
next week it may be better. If someone else wants to have a look at this then 
they should feel free to do so, don't have a problem with rebasing this PR once 
such a change lands.



-- 
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: jira-unsubscr...@kafka.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to