Skip to content

Commit a123ebc

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

3 files changed

Lines changed: 994 additions & 564 deletions

File tree

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

Lines changed: 253 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -18,109 +18,257 @@
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+
@RunWith(AndroidJUnit4::class)
48+
class GenericCopyUtilEspressoTest {
49+
private lateinit var progressHandler: ProgressHandler
50+
private lateinit var copyUtil: GenericCopyUtil
51+
private lateinit var file1: File
52+
private lateinit var file2: File
53+
54+
/**
55+
* Pre-test setup.
56+
*/
57+
@Before
58+
fun setUp() {
59+
progressHandler = ProgressHandler()
60+
copyUtil =
61+
GenericCopyUtil(
62+
InstrumentationRegistry.getInstrumentation().targetContext,
63+
progressHandler,
64+
)
65+
file1 = File.createTempFile("test", "bin").also { it.deleteOnExit() }
66+
file2 = File.createTempFile("test", "bin").also { it.deleteOnExit() }
67+
}
68+
69+
/**
70+
* Test doCopy with small file
71+
*/
72+
@Test
73+
fun testDoCopySmallFile() {
74+
verifyDoCopy(512)
75+
}
76+
77+
/**
78+
* Test doCopy with large file
79+
*/
80+
@Test
81+
fun testDoCopyLargeFile() {
82+
verifyDoCopy(10 * 1024 * 1024)
83+
}
84+
85+
/**
86+
* Test doCopy with empty file
87+
*/
88+
@Test
89+
fun testDoCopyEmptyFile() {
90+
verifyDoCopy(0)
91+
}
92+
93+
private fun verifyDoCopy(size: Int) {
94+
val checksum = DummyFileGenerator.createFile(file1, size)
95+
val progressUpdates = mutableListOf<Long>()
96+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
97+
copyUtil.doCopy(
98+
FileInputStream(file1).channel,
99+
Channels.newChannel(FileOutputStream(file2)),
100+
updatePosition,
101+
)
102+
assertEquals(file1.length(), file2.length())
103+
if (size > 0) {
104+
assertSha1Equals(checksum, file2)
105+
}
106+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
107+
}
108+
109+
/**
110+
* Test copyFile(FileChannel, FileChannel) with small file
111+
*/
112+
@Test
113+
fun testCopyFileChannelSmallFile() {
114+
verifyCopyFileChannel(512)
115+
}
116+
117+
/**
118+
* Test copyFile(FileChannel, FileChannel) with large file
119+
*/
120+
@Test
121+
fun testCopyFileChannelLargeFile() {
122+
verifyCopyFileChannel(10 * 1024 * 1024)
123+
}
124+
125+
/**
126+
* Test copyFile(FileChannel, FileChannel) with empty file
127+
*/
128+
@Test
129+
fun testCopyFileChannelEmptyFile() {
130+
verifyCopyFileChannel(0)
131+
}
132+
133+
private fun verifyCopyFileChannel(size: Int) {
134+
val checksum = DummyFileGenerator.createFile(file1, size)
135+
val progressUpdates = mutableListOf<Long>()
136+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
137+
copyUtil.copyFile(
138+
FileInputStream(file1).channel,
139+
FileOutputStream(file2).channel,
140+
updatePosition,
141+
)
142+
assertEquals(file1.length(), file2.length())
143+
if (size > 0) {
144+
assertSha1Equals(checksum, file2)
145+
}
146+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
147+
}
148+
149+
/**
150+
* Test copyFile(BufferedInputStream, BufferedOutputStream) with small file
151+
*/
152+
@Test
153+
fun testCopyBufferedStreamsSmallFile() {
154+
verifyCopyBufferedStreams(512)
155+
}
156+
157+
/**
158+
* Test copyFile(BufferedInputStream, BufferedOutputStream) with large file
159+
*/
160+
@Test
161+
fun testCopyBufferedStreamsLargeFile() {
162+
verifyCopyBufferedStreams(10 * 1024 * 1024)
163+
}
164+
165+
/**
166+
* Test copyFile(BufferedInputStream, BufferedOutputStream) with empty file
167+
*/
168+
@Test
169+
fun testCopyBufferedStreamsEmptyFile() {
170+
verifyCopyBufferedStreams(0)
171+
}
172+
173+
private fun verifyCopyBufferedStreams(size: Int) {
174+
val checksum = DummyFileGenerator.createFile(file1, size)
175+
val progressUpdates = mutableListOf<Long>()
176+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
177+
copyUtil.copyFile(
178+
BufferedInputStream(FileInputStream(file1)),
179+
BufferedOutputStream(FileOutputStream(file2)),
180+
updatePosition,
181+
)
182+
assertEquals(file1.length(), file2.length())
183+
if (size > 0) {
184+
assertSha1Equals(checksum, file2)
185+
}
186+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
187+
}
188+
189+
/**
190+
* Test copyFile(FileChannel, BufferedOutputStream) for small files
191+
*/
192+
@Test
193+
fun testCopyFileChannelToBufferedOutputStreamSmallFile() {
194+
verifyCopyFileChannelToBufferedOutputStream(512)
195+
}
196+
197+
/**
198+
* Test copyFile(FileChannel, BufferedOutputStream) for large files
199+
*/
200+
@Test
201+
fun testCopyFileChannelToBufferedOutputStreamLargeFile() {
202+
verifyCopyFileChannelToBufferedOutputStream(10 * 1024 * 1024)
203+
}
204+
205+
private fun verifyCopyFileChannelToBufferedOutputStream(size: Int) {
206+
val checksum = DummyFileGenerator.createFile(file1, size)
207+
val progressUpdates = mutableListOf<Long>()
208+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
209+
copyUtil.copyFile(
210+
FileInputStream(file1).channel,
211+
BufferedOutputStream(FileOutputStream(file2)),
212+
updatePosition,
213+
)
214+
assertEquals(file1.length(), file2.length())
215+
if (size > 0) {
216+
assertSha1Equals(checksum, file2)
217+
}
218+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
219+
}
220+
221+
/**
222+
* Test copy cancelled
223+
*/
224+
@Test
225+
fun testCancellation() {
226+
val size = 10 * 1024 * 1024
227+
DummyFileGenerator.createFile(file1, size)
228+
progressHandler.setCancelled(true)
229+
val progressUpdates = mutableListOf<Long>()
230+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
231+
copyUtil.doCopy(
232+
FileInputStream(file1).channel,
233+
Channels.newChannel(FileOutputStream(file2)),
234+
updatePosition,
235+
)
236+
assertTrue(
237+
"Cancelled copy should write less than full size",
238+
file2.length() < file1.length(),
239+
)
240+
}
241+
242+
/**
243+
* Test when copying a large file using the transferTo path, progress updates are batched
244+
*/
245+
@Test
246+
fun testBatchedProgress_transferToPath() {
247+
val size = 10 * 1024 * 1024
248+
DummyFileGenerator.createFile(file1, size)
249+
val progressUpdates = mutableListOf<Long>()
250+
val updatePosition = UpdatePosition { progressUpdates.add(it) }
251+
copyUtil.copyFile(
252+
FileInputStream(file1).channel,
253+
FileOutputStream(file2).channel,
254+
updatePosition,
255+
)
256+
assertEquals("Progress sum should equal file size", file1.length(), progressUpdates.sum())
257+
assertTrue(
258+
"Batched progress should have fewer callbacks (got ${progressUpdates.size})",
259+
progressUpdates.size <= 5,
260+
)
261+
}
262+
263+
private fun assertSha1Equals(
264+
expected: ByteArray,
265+
file: File,
266+
) {
267+
val md = MessageDigest.getInstance("SHA-1")
268+
DigestInputStream(FileInputStream(file), md).use { din ->
269+
val buffer = ByteArray(GenericCopyUtil.DEFAULT_BUFFER_SIZE)
270+
while (din.read(buffer) > -1) { /* consume */ }
271+
}
272+
assertArrayEquals(expected, md.digest())
273+
}
126274
}

0 commit comments

Comments
 (0)