Skip to content

Commit eac945a

Browse files
committed
Migrate to Testcontainers test (and support clamav 1.4 and 1.5)
1 parent 4ff23d3 commit eac945a

File tree

7 files changed

+247
-153
lines changed

7 files changed

+247
-153
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea
2+
*.iml
3+
target/

pom.xml

Lines changed: 93 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,104 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0"
3-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4-
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
55

6-
<modelVersion>4.0.0</modelVersion>
6+
<modelVersion>4.0.0</modelVersion>
77

8-
<parent>
9-
<groupId>org.sonatype.oss</groupId>
10-
<artifactId>oss-parent</artifactId>
11-
<version>7</version>
12-
</parent>
8+
<parent>
9+
<groupId>org.sonatype.oss</groupId>
10+
<artifactId>oss-parent</artifactId>
11+
<version>9</version>
12+
</parent>
1313

14-
<groupId>fi.solita.clamav</groupId>
15-
<artifactId>clamav-client</artifactId>
16-
<version>1.0.1</version>
17-
<packaging>jar</packaging>
18-
<name>Simple ClamAV client</name>
19-
<description>Simple Java client for using clamd INSTREAM scanning in your application.</description>
20-
<url>https://github.com/solita/clamav-java</url>
21-
<licenses>
22-
<license>
23-
<name>GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1</name>
24-
<url>http://www.gnu.org/licenses/lgpl.txt</url>
25-
</license>
26-
</licenses>
27-
<developers>
28-
<developer>
29-
<name>Antti Virtanen</name>
30-
<email>[email protected]</email>
31-
<organization>Solita</organization>
32-
<organizationUrl>http://www.solita.fi</organizationUrl>
33-
</developer>
34-
</developers>
14+
<groupId>fi.solita.clamav</groupId>
15+
<artifactId>clamav-client</artifactId>
16+
<version>1.0.1</version>
17+
<packaging>jar</packaging>
18+
<name>Simple ClamAV client</name>
19+
<description>Simple Java client for using clamd INSTREAM scanning in your application.</description>
20+
<url>https://github.com/solita/clamav-java</url>
21+
<licenses>
22+
<license>
23+
<name>GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1</name>
24+
<url>http://www.gnu.org/licenses/lgpl.txt</url>
25+
</license>
26+
</licenses>
27+
<developers>
28+
<developer>
29+
<name>Antti Virtanen</name>
30+
<email>[email protected]</email>
31+
<organization>Solita</organization>
32+
<organizationUrl>http://www.solita.fi</organizationUrl>
33+
</developer>
34+
</developers>
3535

36-
<properties>
37-
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
38-
</properties>
36+
<properties>
37+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
38+
</properties>
3939

40-
<scm>
41-
<connection>scm:git:git://github.com/solita/clamav-java.git</connection>
42-
<developerConnection>scm:git:[email protected]:solita/clamav-java.git</developerConnection>
43-
<url>https://github.com/solita/clamav-java</url>
44-
</scm>
40+
<scm>
41+
<connection>scm:git:git://github.com/solita/clamav-java.git</connection>
42+
<developerConnection>scm:git:[email protected]:solita/clamav-java.git</developerConnection>
43+
<url>https://github.com/solita/clamav-java</url>
44+
</scm>
4545

46-
<dependencies>
47-
<dependency>
48-
<groupId>junit</groupId>
49-
<artifactId>junit</artifactId>
50-
<version>4.11</version>
51-
<scope>test</scope>
52-
</dependency>
53-
</dependencies>
46+
<dependencies>
47+
<dependency>
48+
<groupId>org.junit.jupiter</groupId>
49+
<artifactId>junit-jupiter</artifactId>
50+
<version>5.14.0</version>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.testcontainers</groupId>
55+
<artifactId>testcontainers</artifactId>
56+
<version>1.21.3</version>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.testcontainers</groupId>
61+
<artifactId>junit-jupiter</artifactId>
62+
<version>1.21.3</version>
63+
<scope>test</scope>
64+
</dependency>
65+
<dependency>
66+
<groupId>org.slf4j</groupId>
67+
<artifactId>slf4j-simple</artifactId>
68+
<version>2.0.17</version>
69+
<scope>test</scope>
70+
</dependency>
71+
<dependency>
72+
<groupId>org.slf4j</groupId>
73+
<artifactId>slf4j-api</artifactId>
74+
<version>2.0.17</version>
75+
<scope>test</scope>
76+
</dependency>
77+
</dependencies>
5478

55-
<build>
56-
<plugins>
57-
<plugin>
58-
<artifactId>maven-compiler-plugin</artifactId>
59-
<version>2.3.2</version>
60-
<configuration>
61-
<source>1.7</source>
62-
<target>1.7</target>
63-
</configuration>
64-
</plugin>
79+
<build>
80+
<plugins>
81+
<plugin>
82+
<artifactId>maven-compiler-plugin</artifactId>
83+
<version>3.14.1</version>
84+
<configuration>
85+
<source>11</source>
86+
<target>11</target>
87+
</configuration>
88+
</plugin>
6589

66-
<plugin>
67-
<groupId>org.sonatype.plugins</groupId>
68-
<artifactId>nexus-staging-maven-plugin</artifactId>
69-
<version>1.6.3</version>
70-
<extensions>true</extensions>
71-
<configuration>
72-
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
73-
<serverId>ossrh-releases-fi.solita</serverId>
74-
<stagingProfileId>ff5044adfb72</stagingProfileId>
75-
</configuration>
76-
</plugin>
90+
<plugin>
91+
<groupId>org.sonatype.plugins</groupId>
92+
<artifactId>nexus-staging-maven-plugin</artifactId>
93+
<version>1.6.3</version>
94+
<extensions>true</extensions>
95+
<configuration>
96+
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
97+
<serverId>ossrh-releases-fi.solita</serverId>
98+
<stagingProfileId>ff5044adfb72</stagingProfileId>
99+
</configuration>
100+
</plugin>
77101

78-
</plugins>
79-
</build>
102+
</plugins>
103+
</build>
80104
</project>

src/main/java/fi/solita/clamav/ClamAVClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public static boolean isCleanReply(byte[] reply) {
139139
private byte[] assertSizeLimit(byte[] reply) {
140140
String r = new String(reply, StandardCharsets.US_ASCII);
141141
if (r.startsWith("INSTREAM size limit exceeded."))
142-
throw new ClamAVSizeLimitException("Clamd size limit exceeded. Full reply from server: " + r);
142+
throw new ClamAVSizeLimitException("Clamd size limit exceeded. Full reply from server: " + r.trim());
143143
return reply;
144144
}
145145

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package fi.solita.clamav;
2+
3+
import org.junit.jupiter.api.AfterAll;
4+
import org.junit.jupiter.api.BeforeAll;
5+
import org.junit.jupiter.params.provider.Arguments;
6+
import org.testcontainers.containers.GenericContainer;
7+
import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
8+
import org.testcontainers.utility.DockerImageName;
9+
10+
import java.util.function.Supplier;
11+
import java.util.stream.Stream;
12+
13+
public abstract class BaseTestcontainers {
14+
@BeforeAll
15+
static void beforeAll() {
16+
ContainerZoo.start();
17+
}
18+
19+
@AfterAll
20+
static void afterAll() {
21+
ContainerZoo.stop();
22+
}
23+
24+
private static Stream<Arguments> provideClamdContainers() {
25+
// INFO: We have to wrap GenericContainer info Supplier wrapper to force JUnit not to close AutoClosable Test method parameter
26+
return Stream.of(
27+
Arguments.of("1.4", (Supplier<GenericContainer<?>>) () -> ContainerZoo.clamav14),
28+
Arguments.of("1.5", (Supplier<GenericContainer<?>>) () -> ContainerZoo.clamav15)
29+
);
30+
}
31+
32+
protected static ClamAVClient client(GenericContainer<?> container) {
33+
return new ClamAVClient("localhost", container.getFirstMappedPort());
34+
}
35+
}
36+
37+
final class ContainerZoo {
38+
public static final GenericContainer<?> clamav14 = new GenericContainer<>(DockerImageName.parse("clamav/clamav:1.4"))
39+
.waitingFor(new HostPortWaitStrategy().forPorts(3310))
40+
.withExposedPorts(3310);
41+
42+
public static final GenericContainer<?> clamav15 = new GenericContainer<>(DockerImageName.parse("clamav/clamav:1.5"))
43+
.waitingFor(new HostPortWaitStrategy().forPorts(3310))
44+
.withExposedPorts(3310);
45+
46+
public static void start() {
47+
clamav14.start();
48+
clamav15.start();
49+
}
50+
51+
public static void stop() {
52+
clamav15.stop();
53+
clamav14.stop();
54+
}
55+
}
Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,71 @@
11
package fi.solita.clamav;
22

3-
import static org.junit.Assert.assertFalse;
4-
import static org.junit.Assert.assertTrue;
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.Disabled;
5+
import org.junit.jupiter.params.ParameterizedTest;
6+
import org.junit.jupiter.params.provider.MethodSource;
7+
import org.testcontainers.containers.GenericContainer;
58

69
import java.io.IOException;
7-
import java.io.InputStream;
8-
import java.net.UnknownHostException;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.function.Supplier;
912

10-
import org.junit.Test;
13+
import static org.junit.jupiter.api.Assertions.*;
1114

12-
/**
13-
* These tests assume clamd is running and responding in the virtual machine.
14-
*/
15-
public class InstreamTest {
15+
public class InstreamTest extends BaseTestcontainers {
16+
@ParameterizedTest
17+
@MethodSource("provideClamdContainers")
18+
public void testRandomBytes(String clamdVersion, Supplier<GenericContainer<?>> containerGetter) throws IOException {
19+
byte[] r = client(containerGetter.get()).scan("alsdklaksdla".getBytes(StandardCharsets.US_ASCII));
20+
assertTrue(ClamAVClient.isCleanReply(r));
21+
}
1622

17-
private static String CLAMAV_HOST = "localhost";
18-
19-
private byte[] scan(byte[] input) throws UnknownHostException, IOException {
20-
ClamAVClient cl = new ClamAVClient(CLAMAV_HOST, 3310);
21-
return cl.scan(input);
22-
}
23-
24-
private byte[] scan(InputStream input) throws UnknownHostException, IOException {
25-
ClamAVClient cl = new ClamAVClient(CLAMAV_HOST, 3310);
26-
return cl.scan(input);
27-
}
28-
@Test
29-
public void testRandomBytes() throws UnknownHostException, IOException {
30-
byte[] r = scan("alsdklaksdla".getBytes("ASCII"));
31-
assertTrue(ClamAVClient.isCleanReply(r));
32-
}
33-
34-
@Test
35-
public void testPositive() throws UnknownHostException, IOException {
36-
// http://www.eicar.org/86-0-Intended-use.html
37-
byte[] EICAR = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*".getBytes("ASCII");
38-
byte[] r = scan(EICAR);
39-
assertFalse(ClamAVClient.isCleanReply(r));
40-
}
23+
@ParameterizedTest
24+
@MethodSource("provideClamdContainers")
25+
public void testPositive(String clamdVersion, Supplier<GenericContainer<?>> containerGetter) throws IOException {
26+
// http://www.eicar.org/86-0-Intended-use.html
27+
byte[] EICAR = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*".getBytes(StandardCharsets.US_ASCII);
28+
byte[] r = client(containerGetter.get()).scan(EICAR);
29+
assertFalse(ClamAVClient.isCleanReply(r));
30+
}
4131

42-
@Test
43-
public void testStreamChunkingWorks() throws UnknownHostException, IOException {
44-
byte[] multipleChunks = new byte[50000];
45-
byte[] r = scan(multipleChunks);
46-
assertTrue(ClamAVClient.isCleanReply(r));
47-
}
48-
49-
@Test
50-
public void testChunkLimit() throws UnknownHostException, IOException {
51-
byte[] maximumChunk = new byte[2048];
52-
byte[] r = scan(maximumChunk);
53-
assertTrue(ClamAVClient.isCleanReply(r));
54-
}
55-
56-
@Test
57-
public void testZeroBytes() throws UnknownHostException, IOException {
58-
byte[] r = scan(new byte[]{});
59-
assertTrue(ClamAVClient.isCleanReply(r));
60-
}
32+
@ParameterizedTest
33+
@MethodSource("provideClamdContainers")
34+
public void testStreamChunkingWorks(String clamdVersion, Supplier<GenericContainer<?>> containerGetter) throws IOException {
35+
byte[] multipleChunks = new byte[50000];
36+
byte[] r = client(containerGetter.get()).scan(multipleChunks);
37+
assertTrue(ClamAVClient.isCleanReply(r));
38+
}
6139

62-
@Test(expected = ClamAVSizeLimitException.class)
63-
public void testSizeLimit() throws UnknownHostException, IOException {
64-
scan(new SlowInputStream());
65-
}
40+
@ParameterizedTest
41+
@MethodSource("provideClamdContainers")
42+
public void testChunkLimit(String clamdVersion, Supplier<GenericContainer<?>> containerGetter) throws IOException {
43+
byte[] maximumChunk = new byte[2048];
44+
byte[] r = client(containerGetter.get()).scan(maximumChunk);
45+
assertTrue(ClamAVClient.isCleanReply(r));
46+
}
47+
48+
@ParameterizedTest
49+
@MethodSource("provideClamdContainers")
50+
public void testZeroBytes(String clamdVersion, Supplier<GenericContainer<?>> containerGetter) throws IOException {
51+
byte[] r = client(containerGetter.get()).scan(new byte[]{});
52+
assertTrue(ClamAVClient.isCleanReply(r));
53+
}
54+
55+
@ParameterizedTest
56+
@MethodSource("provideClamdContainers")
57+
public void testSizeLimit(String clamdVersion, Supplier<GenericContainer<?>> containerGetter) {
58+
ClamAVSizeLimitException exception = Assertions.assertThrows(ClamAVSizeLimitException.class,
59+
() -> client(containerGetter.get()).scan(new SlowInputStream(false)));
60+
assertEquals("Clamd size limit exceeded. Full reply from server: INSTREAM size limit exceeded. ERROR", exception.getMessage());
61+
}
62+
63+
@Disabled
64+
@ParameterizedTest
65+
@MethodSource("provideClamdContainers")
66+
public void testSizeLimitSuperSlow(String clamdVersion, Supplier<GenericContainer<?>> containerGetter) {
67+
ClamAVSizeLimitException exception = Assertions.assertThrows(ClamAVSizeLimitException.class,
68+
() -> client(containerGetter.get()).scan(new SlowInputStream(true)));
69+
assertEquals("Clamd size limit exceeded. Full reply from server: INSTREAM size limit exceeded. ERROR", exception.getMessage());
70+
}
6671
}

0 commit comments

Comments
 (0)