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