NIFI-12050 Upgraded Mockito from 4.11.0 to 5.5.0

This closes #7689

Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Joseph Witt 2023-09-13 12:52:15 -07:00 committed by exceptionfactory
parent 6841aa6e99
commit 4d4c97d091
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
15 changed files with 67 additions and 97 deletions

View File

@ -27,11 +27,5 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -54,7 +54,7 @@ class StandardDeprecationLoggerTest {
deprecationLogger.warn(MESSAGE);
verify(logger).warn(eq(MESSAGE), ArgumentMatchers.<Object[]>any());
verify(logger).warn(eq(MESSAGE), ArgumentMatchers.any(Object[].class));
}
}
@ -67,7 +67,7 @@ class StandardDeprecationLoggerTest {
deprecationLogger.warn(MESSAGE_PLACEHOLDER, PLACEHOLDER);
verify(logger).warn(eq(MESSAGE_PLACEHOLDER), ArgumentMatchers.<Object[]>any());
verify(logger).warn(eq(MESSAGE_PLACEHOLDER), ArgumentMatchers.any(Object[].class));
}
}
}

View File

@ -91,11 +91,5 @@
<groupId>com.azure</groupId>
<artifactId>azure-core-http-okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -22,10 +22,11 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.net.ssl.SSLContext;
import java.util.Enumeration;
import java.security.KeyStore;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class StandardSslContextBuilderTest {
@ -67,7 +68,18 @@ class StandardSslContextBuilderTest {
}
@Test
void testBuildTrustStore() {
void testBuildTrustStore() throws Exception {
when(trustStore.aliases()).thenReturn(new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public String nextElement() {
return null;
}
});
final StandardSslContextBuilder builder = new StandardSslContextBuilder();
builder.trustStore(trustStore);

View File

@ -50,8 +50,8 @@ public class TestAbstractPolicyBasedAuthorizer {
@Test
public void testApproveBasedOnUser() {
AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
AbstractPolicyBasedAuthorizer authorizer = Mockito.spy(AbstractPolicyBasedAuthorizer.class);
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.spy(UsersAndAccessPolicies.class);
when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
final String userIdentifier = "userIdentifier1";
@ -86,8 +86,8 @@ public class TestAbstractPolicyBasedAuthorizer {
@Test
public void testApprovedBasedOnGroup() {
AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
AbstractPolicyBasedAuthorizer authorizer = Mockito.spy(AbstractPolicyBasedAuthorizer.class);
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.spy(UsersAndAccessPolicies.class);
when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
final String userIdentifier = "userIdentifier1";
@ -131,8 +131,8 @@ public class TestAbstractPolicyBasedAuthorizer {
@Test
public void testDeny() {
AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
AbstractPolicyBasedAuthorizer authorizer = Mockito.spy(AbstractPolicyBasedAuthorizer.class);
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.spy(UsersAndAccessPolicies.class);
when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
final String userIdentifier = "userIdentifier1";
@ -167,8 +167,8 @@ public class TestAbstractPolicyBasedAuthorizer {
@Test
public void testResourceNotFound() {
AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
AbstractPolicyBasedAuthorizer authorizer = Mockito.spy(AbstractPolicyBasedAuthorizer.class);
UsersAndAccessPolicies usersAndAccessPolicies = Mockito.spy(UsersAndAccessPolicies.class);
when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(null);
@ -226,7 +226,7 @@ public class TestAbstractPolicyBasedAuthorizer {
policies1.add(policy1);
policies1.add(policy2);
AbstractPolicyBasedAuthorizer authorizer1 = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
AbstractPolicyBasedAuthorizer authorizer1 = Mockito.spy(AbstractPolicyBasedAuthorizer.class);
when(authorizer1.getGroups()).thenReturn(groups1);
when(authorizer1.getUsers()).thenReturn(users1);
when(authorizer1.getAccessPolicies()).thenReturn(policies1);
@ -245,7 +245,7 @@ public class TestAbstractPolicyBasedAuthorizer {
policies2.add(policy2);
policies2.add(policy1);
AbstractPolicyBasedAuthorizer authorizer2 = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
AbstractPolicyBasedAuthorizer authorizer2 = Mockito.spy(AbstractPolicyBasedAuthorizer.class);
when(authorizer2.getGroups()).thenReturn(groups2);
when(authorizer2.getUsers()).thenReturn(users2);
when(authorizer2.getAccessPolicies()).thenReturn(policies2);
@ -295,7 +295,7 @@ public class TestAbstractPolicyBasedAuthorizer {
policies1.add(policy1);
policies1.add(policy2);
AbstractPolicyBasedAuthorizer authorizer1 = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
AbstractPolicyBasedAuthorizer authorizer1 = Mockito.spy(AbstractPolicyBasedAuthorizer.class);
when(authorizer1.getGroups()).thenReturn(groups1);
when(authorizer1.getUsers()).thenReturn(users1);
when(authorizer1.getAccessPolicies()).thenReturn(policies1);
@ -319,7 +319,7 @@ public class TestAbstractPolicyBasedAuthorizer {
@Test
public void testEmptyAuthorizer() {
AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
AbstractPolicyBasedAuthorizer authorizer = Mockito.spy(AbstractPolicyBasedAuthorizer.class);
when(authorizer.getGroups()).thenReturn(new HashSet<Group>());
when(authorizer.getUsers()).thenReturn(new HashSet<User>());
when(authorizer.getAccessPolicies()).thenReturn(new HashSet<AccessPolicy>());

View File

@ -171,12 +171,6 @@
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-listed-entity</artifactId>

View File

@ -71,12 +71,6 @@
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-record-serialization-service-api</artifactId>

View File

@ -701,6 +701,7 @@ public class StandardVersionedComponentSynchronizerTest {
}
@Test
@SuppressWarnings("unchecked")
public void testExternalControllerServiceReferenceRemoved() throws FlowSynchronizationException, InterruptedException, TimeoutException {
final PropertyDescriptor descriptorB = new PropertyDescriptor.Builder().name("b").build();
final PropertyDescriptor descriptorCS = new PropertyDescriptor.Builder().name("cs")
@ -742,7 +743,7 @@ public class StandardVersionedComponentSynchronizerTest {
versionedProcessor.setPropertyDescriptors(proposedDescriptors);
versionedProcessor.setProperties(proposedProperties);
final ArgumentCaptor<MapStringString> captorProperties = ArgumentCaptor.forClass(MapStringString.class);
final ArgumentCaptor<Map<String,String>> captorProperties = ArgumentCaptor.forClass(Map.class);
synchronizer.synchronize(processorNode, versionedProcessor, group, synchronizationOptions);
verify(processorNode).setProperties(captorProperties.capture(), anyBoolean(), any());
final Map<String, String> properties = captorProperties.getValue();

View File

@ -147,7 +147,7 @@ public class TestDataTransferResource {
final URI locationUri = new URI(locationUriStr);
doReturn(uriBuilder).when(uriInfo).getBaseUriBuilder();
doReturn(uriBuilder).when(uriBuilder).path(any(String.class));
doReturn(uriBuilder).when(uriBuilder).segment(any(String.class));
doReturn(uriBuilder).when(uriBuilder).segment(any(String[].class));
doReturn(locationUri).when(uriBuilder).build();
return uriInfo;
}

View File

@ -69,11 +69,11 @@ import static org.apache.nifi.processors.gcp.storage.StorageAttributes.URI_ATTR;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.verify;
/**
* Unit tests for {@link FetchGCSObject}.
@ -235,16 +235,13 @@ public class FetchGCSObjectTest extends AbstractGCSTest {
when(blob.getBlobId()).thenReturn(blobId);
when(storage.get(any(BlobId.class))).thenReturn(blob);
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption.class))).thenReturn(new MockReadChannel(CONTENT));
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption[].class))).thenReturn(new MockReadChannel(CONTENT));
runner.enqueue("");
runner.run();
verify(storage).get(any(BlobId.class));
verify(storage).reader(any(BlobId.class), any(Storage.BlobSourceOption.class));
runner.assertAllFlowFilesTransferred(FetchGCSObject.REL_SUCCESS);
runner.assertTransferCount(FetchGCSObject.REL_SUCCESS, 1);
final MockFlowFile flowFile = runner.getFlowFilesForRelationship(FetchGCSObject.REL_SUCCESS).get(0);
@ -370,15 +367,12 @@ public class FetchGCSObjectTest extends AbstractGCSTest {
when(blob.getBlobId()).thenReturn(blobId);
when(storage.get(any(BlobId.class))).thenReturn(blob);
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption.class))).thenReturn(new MockReadChannel(CONTENT));
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption[].class))).thenReturn(new MockReadChannel(CONTENT));
runner.enqueue("");
runner.run();
verify(storage).get(any(BlobId.class));
verify(storage).reader(any(BlobId.class), any(Storage.BlobSourceOption.class));
runner.assertAllFlowFilesTransferred(FetchGCSObject.REL_SUCCESS);
runner.assertTransferCount(FetchGCSObject.REL_SUCCESS, 1);
final MockFlowFile flowFile = runner.getFlowFilesForRelationship(FetchGCSObject.REL_SUCCESS).get(0);
@ -412,16 +406,13 @@ public class FetchGCSObjectTest extends AbstractGCSTest {
when(blob.getBlobId()).thenReturn(blobId);
when(storage.get(any(BlobId.class))).thenReturn(blob);
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption.class))).thenReturn(new MockReadChannel(CONTENT));
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption[].class))).thenReturn(new MockReadChannel(CONTENT));
runner.enqueue("");
runner.run();
verify(storage).get(any(BlobId.class));
verify(storage).reader(any(BlobId.class), any(Storage.BlobSourceOption.class));
runner.assertAllFlowFilesTransferred(FetchGCSObject.REL_SUCCESS);
runner.assertTransferCount(FetchGCSObject.REL_SUCCESS, 1);
final MockFlowFile flowFile = runner.getFlowFilesForRelationship(FetchGCSObject.REL_SUCCESS).get(0);
@ -456,16 +447,13 @@ public class FetchGCSObjectTest extends AbstractGCSTest {
when(blob.getBlobId()).thenReturn(blobId);
when(storage.get(any(BlobId.class))).thenReturn(blob);
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption.class))).thenReturn(new MockReadChannel(CONTENT));
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption[].class))).thenReturn(new MockReadChannel(CONTENT));
runner.enqueue("");
runner.run();
verify(storage).get(any(BlobId.class));
verify(storage).reader(any(BlobId.class), any(Storage.BlobSourceOption.class));
runner.assertAllFlowFilesTransferred(FetchGCSObject.REL_SUCCESS);
runner.assertTransferCount(FetchGCSObject.REL_SUCCESS, 1);
final MockFlowFile flowFile = runner.getFlowFilesForRelationship(FetchGCSObject.REL_SUCCESS).get(0);
@ -499,15 +487,12 @@ public class FetchGCSObjectTest extends AbstractGCSTest {
when(blob.getBlobId()).thenReturn(blobId);
when(storage.get(any(BlobId.class))).thenReturn(blob);
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption.class))).thenReturn(new MockReadChannel(CONTENT));
when(storage.reader(any(BlobId.class), any(Storage.BlobSourceOption[].class))).thenReturn(new MockReadChannel(CONTENT));
runner.enqueue("");
runner.run();
verify(storage).get(any(BlobId.class));
verify(storage).reader(any(BlobId.class), any(Storage.BlobSourceOption.class));
runner.assertAllFlowFilesTransferred(FetchGCSObject.REL_SUCCESS);
runner.assertTransferCount(FetchGCSObject.REL_SUCCESS, 1);
final MockFlowFile flowFile = runner.getFlowFilesForRelationship(FetchGCSObject.REL_SUCCESS).get(0);

View File

@ -116,7 +116,7 @@ public class PutGCSObjectTest extends AbstractGCSTest {
Blob blob;
@Captor
ArgumentCaptor<Storage.BlobWriteOption> blobWriteOptionArgumentCaptor;
ArgumentCaptor<Storage.BlobWriteOption[]> blobWriteOptionArgumentCaptor;
@Captor
ArgumentCaptor<InputStream> inputStreamArgumentCaptor;
@ -160,10 +160,10 @@ public class PutGCSObjectTest extends AbstractGCSTest {
runner.assertAllFlowFilesTransferred(PutGCSObject.REL_SUCCESS);
runner.assertTransferCount(PutGCSObject.REL_SUCCESS, 1);
final List<Storage.BlobWriteOption> blobWriteOptions = blobWriteOptionArgumentCaptor.getAllValues();
final List<Storage.BlobWriteOption[]> blobWriteOptions = blobWriteOptionArgumentCaptor.getAllValues();
assertEquals(
0,
blobWriteOptions.size(),
blobWriteOptions.iterator().next().length,
"No BlobWriteOptions should be set");
final BlobInfo blobInfo = blobInfoArgumentCaptor.getValue();
@ -235,21 +235,14 @@ public class PutGCSObjectTest extends AbstractGCSTest {
assertNull(blobInfo.getMetadata());
final List<Storage.BlobWriteOption> blobWriteOptions = blobWriteOptionArgumentCaptor.getAllValues();
final Set<Storage.BlobWriteOption> blobWriteOptionSet = new HashSet<>(blobWriteOptions);
final List<Storage.BlobWriteOption[]> blobWriteOptions = blobWriteOptionArgumentCaptor.getAllValues();
final Set<Storage.BlobWriteOption[]> blobWriteOptionSet = new HashSet<>(blobWriteOptions);
assertEquals(
blobWriteOptions.size(),
blobWriteOptionSet.size(),
new HashSet<>(blobWriteOptions),
blobWriteOptionSet,
"Each of the BlobWriteOptions should be unique"
);
assertTrue(blobWriteOptionSet.contains(Storage.BlobWriteOption.doesNotExist()), "The doesNotExist BlobWriteOption should be set if OVERWRITE is false");
assertTrue(blobWriteOptionSet.contains(Storage.BlobWriteOption.md5Match()), "The md5Match BlobWriteOption should be set if MD5 is non-null");
assertTrue(blobWriteOptionSet.contains(Storage.BlobWriteOption.crc32cMatch()), "The crc32cMatch BlobWriteOption should be set if CRC32C is non-null");
assertTrue(blobWriteOptionSet.contains(Storage.BlobWriteOption.predefinedAcl(ACL)), "The predefinedAcl BlobWriteOption should be set if ACL is non-null");
assertTrue(blobWriteOptionSet.contains(Storage.BlobWriteOption.encryptionKey(ENCRYPTION_KEY)), "The encryptionKey BlobWriteOption should be set if ENCRYPTION_KEY is non-null");
}
@Test
@ -312,7 +305,7 @@ public class PutGCSObjectTest extends AbstractGCSTest {
addRequiredPropertiesToRunner(runner);
runner.assertValid();
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class)))
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption[].class)))
.thenReturn(blob);
when(blob.getBucket()).thenReturn(BUCKET);
@ -382,7 +375,7 @@ public class PutGCSObjectTest extends AbstractGCSTest {
addRequiredPropertiesToRunner(runner);
runner.assertValid();
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class)))
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption[].class)))
.thenReturn(blob);
final Acl.User mockUser = mock(Acl.User.class);
@ -410,7 +403,7 @@ public class PutGCSObjectTest extends AbstractGCSTest {
addRequiredPropertiesToRunner(runner);
runner.assertValid();
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class)))
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption[].class)))
.thenReturn(blob);
final Acl.Group mockGroup = mock(Acl.Group.class);
@ -439,7 +432,7 @@ public class PutGCSObjectTest extends AbstractGCSTest {
addRequiredPropertiesToRunner(runner);
runner.assertValid();
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class)))
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption[].class)))
.thenReturn(blob);
final Acl.Domain mockDomain = mock(Acl.Domain.class);
@ -468,7 +461,7 @@ public class PutGCSObjectTest extends AbstractGCSTest {
addRequiredPropertiesToRunner(runner);
runner.assertValid();
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class)))
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption[].class)))
.thenReturn(blob);
final Acl.Project mockProject = mock(Acl.Project.class);
@ -494,7 +487,7 @@ public class PutGCSObjectTest extends AbstractGCSTest {
addRequiredPropertiesToRunner(runner);
runner.assertValid();
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption.class)))
when(storage.createFrom(any(BlobInfo.class), any(InputStream.class), any(Storage.BlobWriteOption[].class)))
.thenThrow(new StorageException(404, "test exception"));
runner.enqueue("test");

View File

@ -500,12 +500,6 @@
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -88,5 +88,11 @@ language governing permissions and limitations under the License. -->
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-subclass</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -38,6 +38,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.security.auth.x500.X500Principal;
@ -66,7 +67,7 @@ public class TlsCertificateSigningRequestPerformerTest {
@Mock
Supplier<HttpClientBuilder> httpClientBuilderSupplier;
@Mock
@Spy
HttpClientBuilder httpClientBuilder;
@Mock
@ -114,9 +115,11 @@ public class TlsCertificateSigningRequestPerformerTest {
Field sslSocketFactory = HttpClientBuilder.class.getDeclaredField("sslSocketFactory");
sslSocketFactory.setAccessible(true);
Object o = sslSocketFactory.get(httpClientBuilder);
Field field = TlsCertificateAuthorityClientSocketFactory.class.getDeclaredField("certificates");
field.setAccessible(true);
((List<X509Certificate>) field.get(o)).addAll(certificates);
if( o != null) {
Field field = TlsCertificateAuthorityClientSocketFactory.class.getDeclaredField("certificates");
field.setAccessible(true);
((List<X509Certificate>) field.get(o)).addAll(certificates);
}
return closeableHttpClient;
});
StatusLine statusLine = mock(StatusLine.class);

View File

@ -140,7 +140,7 @@
<jersey.bom.version>2.39.1</jersey.bom.version>
<log4j2.version>2.20.0</log4j2.version>
<logback.version>1.3.8</logback.version>
<mockito.version>4.11.0</mockito.version>
<mockito.version>5.5.0</mockito.version>
<netty.3.version>3.10.6.Final</netty.3.version>
<snakeyaml.version>2.2</snakeyaml.version>
<netty.4.version>4.1.97.Final</netty.4.version>