Skip to content

Commit 0368390

Browse files
committed
GenericCopyUtil improvements
- Convert to Kotlin - More tests - Bigger buffer size for copy from/to OTG storage
1 parent be9db40 commit 0368390

4 files changed

Lines changed: 1097 additions & 575 deletions

File tree

app/src/androidTest/java/com/amaze/filemanager/filesystem/files/GenericCopyUtilEspressoTest.kt

Lines changed: 281 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -18,109 +18,285 @@
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
2020

21-
package com.amaze.filemanager.filesystem.files;
22-
23-
import static org.junit.Assert.assertArrayEquals;
24-
import static org.junit.Assert.assertEquals;
25-
26-
import java.io.BufferedInputStream;
27-
import java.io.BufferedOutputStream;
28-
import java.io.File;
29-
import java.io.FileInputStream;
30-
import java.io.FileOutputStream;
31-
import java.io.IOException;
32-
import java.nio.channels.Channels;
33-
import java.security.DigestInputStream;
34-
import java.security.MessageDigest;
35-
import java.security.NoSuchAlgorithmException;
36-
37-
import org.junit.Before;
38-
import org.junit.Test;
39-
import org.junit.runner.RunWith;
40-
41-
import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil;
42-
import com.amaze.filemanager.test.DummyFileGenerator;
43-
import com.amaze.filemanager.utils.ProgressHandler;
44-
45-
import androidx.test.ext.junit.runners.AndroidJUnit4;
46-
import androidx.test.platform.app.InstrumentationRegistry;
47-
48-
@RunWith(AndroidJUnit4.class)
49-
public class GenericCopyUtilEspressoTest {
50-
51-
private GenericCopyUtil copyUtil;
52-
53-
private File file1, file2;
54-
55-
@Before
56-
public void setUp() throws IOException {
57-
copyUtil =
58-
new GenericCopyUtil(
59-
InstrumentationRegistry.getInstrumentation().getTargetContext(), new ProgressHandler());
60-
file1 = File.createTempFile("test", "bin");
61-
file2 = File.createTempFile("test", "bin");
62-
file1.deleteOnExit();
63-
file2.deleteOnExit();
64-
}
65-
66-
@Test
67-
public void testCopyFile1() throws IOException, NoSuchAlgorithmException {
68-
doTestCopyFile1(512);
69-
doTestCopyFile1(10 * 1024 * 1024);
70-
}
71-
72-
@Test
73-
public void testCopyFile2() throws IOException, NoSuchAlgorithmException {
74-
doTestCopyFile2(512);
75-
doTestCopyFile2(10 * 1024 * 1024);
76-
}
77-
78-
@Test
79-
public void testCopyFile3() throws IOException, NoSuchAlgorithmException {
80-
doTestCopyFile3(512);
81-
doTestCopyFile3(10 * 1024 * 1024);
82-
}
83-
84-
// doCopy(ReadableByteChannel in, WritableByteChannel out)
85-
private void doTestCopyFile1(int size) throws IOException, NoSuchAlgorithmException {
86-
byte[] checksum = DummyFileGenerator.createFile(file1, size);
87-
copyUtil.doCopy(
88-
new FileInputStream(file1).getChannel(),
89-
Channels.newChannel(new FileOutputStream(file2)),
90-
ServiceWatcherUtil.UPDATE_POSITION);
91-
assertEquals(file1.length(), file2.length());
92-
assertSha1Equals(checksum, file2);
93-
}
94-
95-
// copy(FileChannel in, FileChannel out)
96-
private void doTestCopyFile2(int size) throws IOException, NoSuchAlgorithmException {
97-
byte[] checksum = DummyFileGenerator.createFile(file1, size);
98-
copyUtil.copyFile(
99-
new FileInputStream(file1).getChannel(),
100-
new FileOutputStream(file2).getChannel(),
101-
ServiceWatcherUtil.UPDATE_POSITION);
102-
assertEquals(file1.length(), file2.length());
103-
assertSha1Equals(checksum, file2);
104-
}
105-
106-
// copy(BufferedInputStream in, BufferedOutputStream out)
107-
private void doTestCopyFile3(int size) throws IOException, NoSuchAlgorithmException {
108-
byte[] checksum = DummyFileGenerator.createFile(file1, size);
109-
copyUtil.copyFile(
110-
new BufferedInputStream(new FileInputStream(file1)),
111-
new BufferedOutputStream(new FileOutputStream(file2)),
112-
ServiceWatcherUtil.UPDATE_POSITION);
113-
assertEquals(file1.length(), file2.length());
114-
assertSha1Equals(checksum, file2);
115-
}
116-
117-
private void assertSha1Equals(byte[] expected, File file)
118-
throws NoSuchAlgorithmException, IOException {
119-
MessageDigest md = MessageDigest.getInstance("SHA-1");
120-
DigestInputStream in = new DigestInputStream(new FileInputStream(file), md);
121-
byte[] buffer = new byte[GenericCopyUtil.DEFAULT_BUFFER_SIZE];
122-
while (in.read(buffer) > -1) {}
123-
in.close();
124-
assertArrayEquals(expected, md.digest());
125-
}
21+
package com.amaze.filemanager.filesystem.files
22+
23+
import androidx.test.ext.junit.runners.AndroidJUnit4
24+
import androidx.test.platform.app.InstrumentationRegistry
25+
import com.amaze.filemanager.fileoperations.utils.UpdatePosition
26+
import com.amaze.filemanager.test.DummyFileGenerator
27+
import com.amaze.filemanager.utils.ProgressHandler
28+
import org.junit.Assert.assertArrayEquals
29+
import org.junit.Assert.assertEquals
30+
import org.junit.Assert.assertTrue
31+
import org.junit.Before
32+
import org.junit.Test
33+
import org.junit.runner.RunWith
34+
import java.io.BufferedInputStream
35+
import java.io.BufferedOutputStream
36+
import java.io.File
37+
import java.io.FileInputStream
38+
import java.io.FileOutputStream
39+
import java.nio.channels.Channels
40+
import java.security.DigestInputStream
41+
import java.security.MessageDigest
42+
43+
/**
44+
* Instrumented tests for [GenericCopyUtil] to verify the correctness of file copying
45+
* and progress updates.
46+
*/
47+
@Suppress("StringLiteralDuplication")
48+
@RunWith(AndroidJUnit4::class)
49+
class GenericCopyUtilEspressoTest {
50+
private lateinit var progressHandler: ProgressHandler
51+
private lateinit var copyUtil: GenericCopyUtil
52+
private lateinit var file1: File
53+
private lateinit var file2: File
54+
55+
/**
56+
* Pre-test setup.
57+
*/
58+
@Before
59+
fun setUp() {
60+
progressHandler = ProgressHandler()
61+
copyUtil =
62+
GenericCopyUtil(
63+
InstrumentationRegistry.getInstrumentation().targetContext,
64+
progressHandler,
65+
)
66+
file1 = File.createTempFile("test", "bin").also { it.deleteOnExit() }
67+
file2 = File.createTempFile("test", "bin").also { it.deleteOnExit() }
68+
}
69+
70+
/**
71+
* Test doCopy with small file
72+
*/
73+
@Test
74+
fun testDoCopySmallFile() {
75+
verifyDoCopy(512)
76+
}
77+
78+
/**
79+
* Test doCopy with large file
80+
*/
81+
@Test
82+
fun testDoCopyLargeFile() {
83+
verifyDoCopy(10 * 1024 * 1024)
84+
}
85+
86+
/**
87+
* Test doCopy with empty file
88+
*/
89+
@Test
90+
fun testDoCopyEmptyFile() {
91+
verifyDoCopy(0)
92+
}
93+
94+
private fun verifyDoCopy(size: Int) {
95+
val checksum = DummyFileGenerator.createFile(file1, size)
96+
val progressUpdates = mutableListOf<Long>()
97+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
98+
FileInputStream(file1).channel.use { fin ->
99+
Channels.newChannel(FileOutputStream(file2)).use { fout ->
100+
copyUtil.doCopy(
101+
fin,
102+
fout,
103+
updatePosition,
104+
)
105+
}
106+
}
107+
108+
assertEquals(file1.length(), file2.length())
109+
if (size > 0) {
110+
assertSha1Equals(checksum, file2)
111+
}
112+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
113+
}
114+
115+
/**
116+
* Test copyFile(FileChannel, FileChannel) with small file
117+
*/
118+
@Test
119+
fun testCopyFileChannelSmallFile() {
120+
verifyCopyFileChannel(512)
121+
}
122+
123+
/**
124+
* Test copyFile(FileChannel, FileChannel) with large file
125+
*/
126+
@Test
127+
fun testCopyFileChannelLargeFile() {
128+
verifyCopyFileChannel(10 * 1024 * 1024)
129+
}
130+
131+
/**
132+
* Test copyFile(FileChannel, FileChannel) with empty file
133+
*/
134+
@Test
135+
fun testCopyFileChannelEmptyFile() {
136+
verifyCopyFileChannel(0)
137+
}
138+
139+
private fun verifyCopyFileChannel(size: Int) {
140+
val checksum = DummyFileGenerator.createFile(file1, size)
141+
val progressUpdates = mutableListOf<Long>()
142+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
143+
FileInputStream(file1).channel.use { fin ->
144+
FileOutputStream(file2).channel.use { fout ->
145+
copyUtil.copyFile(
146+
fin,
147+
fout,
148+
updatePosition,
149+
)
150+
}
151+
}
152+
assertEquals(file1.length(), file2.length())
153+
if (size > 0) {
154+
assertSha1Equals(checksum, file2)
155+
}
156+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
157+
}
158+
159+
/**
160+
* Test copyFile(BufferedInputStream, BufferedOutputStream) with small file
161+
*/
162+
@Test
163+
fun testCopyBufferedStreamsSmallFile() {
164+
verifyCopyBufferedStreams(512)
165+
}
166+
167+
/**
168+
* Test copyFile(BufferedInputStream, BufferedOutputStream) with large file
169+
*/
170+
@Test
171+
fun testCopyBufferedStreamsLargeFile() {
172+
verifyCopyBufferedStreams(10 * 1024 * 1024)
173+
}
174+
175+
/**
176+
* Test copyFile(BufferedInputStream, BufferedOutputStream) with empty file
177+
*/
178+
@Test
179+
fun testCopyBufferedStreamsEmptyFile() {
180+
verifyCopyBufferedStreams(0)
181+
}
182+
183+
private fun verifyCopyBufferedStreams(size: Int) {
184+
val checksum = DummyFileGenerator.createFile(file1, size)
185+
val progressUpdates = mutableListOf<Long>()
186+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
187+
BufferedInputStream(FileInputStream(file1)).use { fin ->
188+
BufferedOutputStream(FileOutputStream(file2)).use { fout ->
189+
copyUtil.copyFile(
190+
fin,
191+
fout,
192+
updatePosition,
193+
)
194+
}
195+
}
196+
assertEquals(file1.length(), file2.length())
197+
if (size > 0) {
198+
assertSha1Equals(checksum, file2)
199+
}
200+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
201+
}
202+
203+
/**
204+
* Test copyFile(FileChannel, BufferedOutputStream) for small files
205+
*/
206+
@Test
207+
fun testCopyFileChannelToBufferedOutputStreamSmallFile() {
208+
verifyCopyFileChannelToBufferedOutputStream(512)
209+
}
210+
211+
/**
212+
* Test copyFile(FileChannel, BufferedOutputStream) for large files
213+
*/
214+
@Test
215+
fun testCopyFileChannelToBufferedOutputStreamLargeFile() {
216+
verifyCopyFileChannelToBufferedOutputStream(10 * 1024 * 1024)
217+
}
218+
219+
private fun verifyCopyFileChannelToBufferedOutputStream(size: Int) {
220+
val checksum = DummyFileGenerator.createFile(file1, size)
221+
val progressUpdates = mutableListOf<Long>()
222+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
223+
FileInputStream(file1).channel.use { fin ->
224+
BufferedOutputStream(FileOutputStream(file2)).use { fout ->
225+
copyUtil.copyFile(
226+
fin,
227+
fout,
228+
updatePosition,
229+
)
230+
}
231+
}
232+
233+
assertEquals(file1.length(), file2.length())
234+
if (size > 0) {
235+
assertSha1Equals(checksum, file2)
236+
}
237+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
238+
}
239+
240+
/**
241+
* Test copy cancelled
242+
*/
243+
@Test
244+
fun testCancellation() {
245+
val size = 10 * 1024 * 1024
246+
DummyFileGenerator.createFile(file1, size)
247+
progressHandler.setCancelled(true)
248+
val progressUpdates = mutableListOf<Long>()
249+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
250+
FileInputStream(file1).channel.use { fin ->
251+
Channels.newChannel(FileOutputStream(file2)).use { fout ->
252+
copyUtil.doCopy(
253+
fin,
254+
fout,
255+
updatePosition,
256+
)
257+
}
258+
}
259+
260+
assertTrue(
261+
"Cancelled copy should write less than full size",
262+
file2.length() < file1.length(),
263+
)
264+
}
265+
266+
/**
267+
* Test when copying a large file using the transferTo path, progress updates are batched
268+
*/
269+
@Test
270+
fun testBatchedProgress_transferToPath() {
271+
val size = 10 * 1024 * 1024
272+
DummyFileGenerator.createFile(file1, size)
273+
val progressUpdates = mutableListOf<Long>()
274+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
275+
FileInputStream(file1).channel.use { fin ->
276+
FileOutputStream(file2).channel.use { fout ->
277+
copyUtil.copyFile(
278+
fin,
279+
fout,
280+
updatePosition,
281+
)
282+
}
283+
}
284+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
285+
assertTrue(
286+
"Batched progress should have fewer callbacks (got ${progressUpdates.size})",
287+
progressUpdates.size <= 5,
288+
)
289+
}
290+
291+
private fun assertSha1Equals(
292+
expected: ByteArray,
293+
file: File,
294+
) {
295+
val md = MessageDigest.getInstance("SHA-1")
296+
DigestInputStream(FileInputStream(file), md).use { din ->
297+
val buffer = ByteArray(GenericCopyUtil.DEFAULT_BUFFER_SIZE)
298+
while (din.read(buffer) > -1) { /* consume */ }
299+
}
300+
assertArrayEquals(expected, md.digest())
301+
}
126302
}

0 commit comments

Comments
 (0)