2023-10-22 12:51:31 +02:00
|
|
|
import Setting from '@joplin/lib/models/Setting';
|
|
|
|
import shim from '@joplin/lib/shim';
|
|
|
|
import uuid from '@joplin/lib/uuid';
|
|
|
|
import { join } from 'path';
|
|
|
|
import FsDriverBase from '@joplin/lib/fs-driver-base';
|
|
|
|
import Logger from '@joplin/utils/Logger';
|
|
|
|
import { Buffer } from 'buffer';
|
2024-03-06 12:03:11 +02:00
|
|
|
import createFilesFromPathRecord from './testUtil/createFilesFromPathRecord';
|
|
|
|
import verifyDirectoryMatches from './testUtil/verifyDirectoryMatches';
|
2023-10-22 12:51:31 +02:00
|
|
|
|
|
|
|
const logger = Logger.create('fs-driver-tests');
|
|
|
|
|
|
|
|
const expectToBe = async <T> (actual: T, expected: T) => {
|
|
|
|
if (actual !== expected) {
|
|
|
|
throw new Error(`Integration test failure: ${actual} was expected to be ${expected}`);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const testExpect = async () => {
|
|
|
|
// Verify that expect is working
|
|
|
|
await expectToBe(1, 1);
|
|
|
|
await expectToBe(true, true);
|
|
|
|
|
|
|
|
let failed = false;
|
|
|
|
try {
|
|
|
|
await expectToBe('a', 'test');
|
|
|
|
failed = true;
|
|
|
|
} catch (_error) {
|
|
|
|
failed = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (failed) {
|
|
|
|
throw new Error('expectToBe should throw when given non-equal inputs');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const testAppendFile = async (tempDir: string) => {
|
|
|
|
logger.info('Testing fsDriver.appendFile...');
|
|
|
|
|
|
|
|
const targetFile = join(tempDir, uuid.createNano());
|
|
|
|
|
|
|
|
const fsDriver: FsDriverBase = shim.fsDriver();
|
|
|
|
|
|
|
|
// For fs-driver-rn's appendFile to work, we first need to create the file.
|
|
|
|
// TODO: This is different from the requirements of fs-driver-node.
|
|
|
|
await fsDriver.writeFile(targetFile, '');
|
|
|
|
|
|
|
|
const firstChunk = 'A 𝓊𝓃𝒾𝒸𝓸𝒹𝓮 test\n...';
|
|
|
|
await fsDriver.appendFile(targetFile, firstChunk, 'utf-8');
|
|
|
|
await expectToBe(await fsDriver.readFile(targetFile), firstChunk);
|
|
|
|
|
|
|
|
const secondChunk = '▪️ More unicode ▪️';
|
|
|
|
await fsDriver.appendFile(targetFile, secondChunk, 'utf8');
|
|
|
|
await expectToBe(await fsDriver.readFile(targetFile), firstChunk + secondChunk);
|
|
|
|
|
|
|
|
const thirdChunk = 'ASCII';
|
|
|
|
await fsDriver.appendFile(targetFile, thirdChunk, 'ascii');
|
|
|
|
await expectToBe(await fsDriver.readFile(targetFile), firstChunk + secondChunk + thirdChunk);
|
|
|
|
|
|
|
|
const lastChunk = 'Test...';
|
|
|
|
await fsDriver.appendFile(
|
|
|
|
targetFile, Buffer.from(lastChunk, 'utf8').toString('base64'), 'base64',
|
|
|
|
);
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFile(targetFile), firstChunk + secondChunk + thirdChunk + lastChunk,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Should throw if given an invalid encoding
|
|
|
|
let didThrow = false;
|
|
|
|
try {
|
|
|
|
await fsDriver.appendFile(targetFile, 'test', 'bad-encoding');
|
|
|
|
} catch (_error) {
|
|
|
|
didThrow = true;
|
|
|
|
}
|
|
|
|
await expectToBe(didThrow, true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const testReadWriteFileUtf8 = async (tempDir: string) => {
|
|
|
|
logger.info('Testing fsDriver.writeFile and fsDriver.readFile with utf-8...');
|
|
|
|
|
|
|
|
const filePath = join(tempDir, uuid.createNano());
|
|
|
|
|
|
|
|
const testStrings = [
|
|
|
|
// ASCII
|
|
|
|
'test',
|
|
|
|
|
|
|
|
// Special characters
|
|
|
|
'𝐴 𝒕𝐞𝑺𝒕',
|
|
|
|
|
|
|
|
// Emojis
|
|
|
|
'✅ Test. 🕳️',
|
|
|
|
];
|
|
|
|
|
|
|
|
const testEncodings = ['utf-8', 'utf8', 'UtF-8'];
|
|
|
|
|
|
|
|
// Use the same file for all tests to test overwriting
|
|
|
|
for (const encoding of testEncodings) {
|
|
|
|
for (const testString of testStrings) {
|
|
|
|
const fsDriver: FsDriverBase = shim.fsDriver();
|
|
|
|
await fsDriver.writeFile(filePath, testString, encoding);
|
|
|
|
|
|
|
|
const fileData = await fsDriver.readFile(filePath, encoding);
|
|
|
|
await expectToBe(fileData, testString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const testReadFileChunkUtf8 = async (tempDir: string) => {
|
|
|
|
logger.info('Testing fsDriver.readFileChunk...');
|
|
|
|
|
|
|
|
const filePath = join(tempDir, `${uuid.createNano()}.txt`);
|
|
|
|
|
|
|
|
const fsDriver: FsDriverBase = shim.fsDriver();
|
|
|
|
|
|
|
|
// 🕳️ is 7 bytes when utf-8 encoded
|
|
|
|
// à,á,â, and ã are each 2 bytes
|
|
|
|
const expectedFileContent = '01234567\nàáâã\n🕳️🕳️🕳️\ntēst...';
|
|
|
|
await fsDriver.writeFile(filePath, expectedFileContent, 'utf8');
|
|
|
|
|
|
|
|
const testEncodings = ['utf-8', 'utf8', 'UtF-8'];
|
|
|
|
|
|
|
|
for (const encoding of testEncodings) {
|
|
|
|
const handle = await fsDriver.open(filePath, 'r');
|
|
|
|
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 8, encoding), '01234567',
|
|
|
|
);
|
|
|
|
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 1, encoding), '\n',
|
|
|
|
);
|
|
|
|
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 8, encoding), 'àáâã',
|
|
|
|
);
|
|
|
|
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 8, encoding), '\n🕳️',
|
|
|
|
);
|
|
|
|
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 15, encoding), '🕳️🕳️\n',
|
|
|
|
);
|
|
|
|
|
|
|
|
// A 0 length should return null and not advance
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 0, encoding), null,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Reading a different encoding (then switching back to the original)
|
|
|
|
// should be supported
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 3, 'base64'),
|
|
|
|
Buffer.from('tē', 'utf-8').toString('base64'),
|
|
|
|
);
|
|
|
|
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 100, encoding), 'st...',
|
|
|
|
);
|
|
|
|
|
|
|
|
// Should not be able to read past the end
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 10, encoding), null,
|
|
|
|
);
|
|
|
|
|
|
|
|
await expectToBe(
|
|
|
|
await fsDriver.readFileChunk(handle, 1, encoding), null,
|
|
|
|
);
|
|
|
|
|
2024-08-02 15:51:49 +02:00
|
|
|
await fsDriver.close(handle);
|
2023-10-22 12:51:31 +02:00
|
|
|
}
|
2023-11-10 16:22:26 +02:00
|
|
|
|
|
|
|
// Should throw when the file doesn't exist
|
|
|
|
let readData = undefined;
|
|
|
|
try {
|
|
|
|
const handle = await fsDriver.open(`${filePath}.noexist`, 'r');
|
|
|
|
readData = await fsDriver.readFileChunk(handle, 1, 'utf8');
|
|
|
|
} catch (error) {
|
|
|
|
await expectToBe(error.code, 'ENOENT');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should not have read any data
|
|
|
|
await expectToBe(readData, undefined);
|
2023-10-22 12:51:31 +02:00
|
|
|
};
|
|
|
|
|
2024-03-06 12:03:11 +02:00
|
|
|
const testTarCreateAndExtract = async (tempDir: string) => {
|
2023-10-22 12:51:31 +02:00
|
|
|
logger.info('Testing fsDriver.tarCreate...');
|
|
|
|
|
|
|
|
const directoryToPack = join(tempDir, uuid.createNano());
|
|
|
|
|
|
|
|
const fsDriver: FsDriverBase = shim.fsDriver();
|
|
|
|
|
|
|
|
// Add test files to the directory
|
|
|
|
const fileContents: Record<string, string> = {};
|
|
|
|
|
|
|
|
// small utf-8 encoded files
|
|
|
|
for (let i = 0; i < 10; i ++) {
|
2024-03-06 12:03:11 +02:00
|
|
|
const testFileName = uuid.createNano();
|
2023-10-22 12:51:31 +02:00
|
|
|
const fileContent = `✅ Testing... ä ✅ File #${i}`;
|
|
|
|
|
2024-03-06 12:03:11 +02:00
|
|
|
fileContents[testFileName] = fileContent;
|
2023-10-22 12:51:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// larger utf-8 encoded files
|
|
|
|
for (let i = 0; i < 3; i ++) {
|
2024-03-06 12:03:11 +02:00
|
|
|
const testFileName = uuid.createNano();
|
2023-10-22 12:51:31 +02:00
|
|
|
|
|
|
|
let fileContent = `✅ Testing... ä ✅ File #${i}`;
|
|
|
|
for (let j = 0; j < 8; j ++) {
|
|
|
|
fileContent += fileContent;
|
|
|
|
}
|
|
|
|
|
2024-03-06 12:03:11 +02:00
|
|
|
fileContents[testFileName] = fileContent;
|
2023-10-22 12:51:31 +02:00
|
|
|
}
|
|
|
|
|
2024-03-06 12:03:11 +02:00
|
|
|
await createFilesFromPathRecord(directoryToPack, fileContents);
|
|
|
|
|
2023-10-22 12:51:31 +02:00
|
|
|
// Pack the files
|
|
|
|
const pathsToTar = Object.keys(fileContents);
|
|
|
|
const tarOutputPath = join(tempDir, 'test-tar.tar');
|
|
|
|
await fsDriver.tarCreate({
|
2024-03-06 12:03:11 +02:00
|
|
|
cwd: directoryToPack,
|
2023-10-22 12:51:31 +02:00
|
|
|
file: tarOutputPath,
|
|
|
|
}, pathsToTar);
|
|
|
|
|
|
|
|
// Read the tar file as utf-8 and search for the written file contents
|
|
|
|
// (which should work).
|
|
|
|
const rawTarData: string = await fsDriver.readFile(tarOutputPath, 'utf8');
|
|
|
|
|
|
|
|
for (const fileContent of Object.values(fileContents)) {
|
|
|
|
await expectToBe(rawTarData.includes(fileContent), true);
|
|
|
|
}
|
2024-03-06 12:03:11 +02:00
|
|
|
|
|
|
|
logger.info('Testing fsDriver.tarExtract...');
|
|
|
|
|
|
|
|
const outputDirectory = join(tempDir, uuid.createNano());
|
|
|
|
await fsDriver.mkdir(outputDirectory);
|
|
|
|
await fsDriver.tarExtract({
|
|
|
|
cwd: outputDirectory,
|
|
|
|
file: tarOutputPath,
|
|
|
|
});
|
|
|
|
|
|
|
|
await verifyDirectoryMatches(outputDirectory, fileContents);
|
|
|
|
};
|
|
|
|
|
|
|
|
const testMd5File = async (tempDir: string) => {
|
|
|
|
logger.info('Testing fsDriver.md5file...');
|
|
|
|
const fsDriver = shim.fsDriver();
|
|
|
|
|
|
|
|
const testFilePath = join(tempDir, `test-md5-${uuid.createNano()}`);
|
|
|
|
await fsDriver.writeFile(testFilePath, '🚧test', 'utf8');
|
|
|
|
|
|
|
|
await expectToBe(await fsDriver.md5File(testFilePath), 'ba11ba1be5042133a71874731e3d42cd');
|
2023-10-22 12:51:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// In the past, some fs-driver functionality has worked correctly on some devices and not others.
|
|
|
|
// As such, we need to be able to run some tests on-device.
|
|
|
|
const runOnDeviceTests = async () => {
|
|
|
|
const tempDir = join(Setting.value('tempDir'), uuid.createNano());
|
|
|
|
|
|
|
|
if (await shim.fsDriver().exists(tempDir)) {
|
|
|
|
await shim.fsDriver().remove(tempDir);
|
|
|
|
}
|
2024-08-02 15:51:49 +02:00
|
|
|
await shim.fsDriver().mkdir(tempDir);
|
2023-10-22 12:51:31 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
await testExpect();
|
|
|
|
await testAppendFile(tempDir);
|
|
|
|
await testReadWriteFileUtf8(tempDir);
|
|
|
|
await testReadFileChunkUtf8(tempDir);
|
2024-03-06 12:03:11 +02:00
|
|
|
await testTarCreateAndExtract(tempDir);
|
|
|
|
await testMd5File(tempDir);
|
|
|
|
|
|
|
|
logger.info('Done');
|
2023-10-22 12:51:31 +02:00
|
|
|
} catch (error) {
|
|
|
|
const errorMessage = `On-device testing failed with an exception: ${error}.`;
|
|
|
|
|
|
|
|
logger.error(errorMessage, error);
|
|
|
|
alert(errorMessage);
|
|
|
|
} finally {
|
|
|
|
await shim.fsDriver().remove(tempDir);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export default runOnDeviceTests;
|