1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-24 08:45:10 +03:00
esp8266/tests/host/fs/test_fs.inc
Earle F. Philhower, III 35d22edeec
Rationalize File timestamp callback (#7785)
Fixes #7775

Clean up the passing/setting of custom File time callbacks and add a
host test verifying they work.  Existing core was not passing custom
timeCallbacks set at the FS level down to open()ed files, resulting in
them calling the default time(nullptr) and reporting wrong file modify
times.
2020-12-22 10:41:11 +01:00

440 lines
14 KiB
C++

static void createFile (const char* name, const char* content)
{
auto f = FSTYPE.open(name, "w");
REQUIRE(f);
if (content) {
f.print(content);
}
}
static String readFile (const char* name)
{
auto f = FSTYPE.open(name, "r");
if (f) {
return f.readString();
}
return String();
}
static std::set<String> listDir (const char* path)
{
std::set<String> result;
Dir dir = FSTYPE.openDir(path);
while (dir.next()) {
REQUIRE(result.find(dir.fileName()) == std::end(result));
result.insert(dir.fileName());
}
return result;
}
TEST_CASE(TESTPRE "FS can begin",TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
}
TEST_CASE(TESTPRE "FS can't begin with zero size",TESTPAT)
{
FS_MOCK_DECLARE(0, 8, 512, "");
REQUIRE_FALSE(FSTYPE.begin());
}
TEST_CASE(TESTPRE "Before begin is called, open will fail",TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE_FALSE(FSTYPE.open("/foo", "w"));
}
TEST_CASE(TESTPRE "FS can create file",TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/test", "");
REQUIRE(FSTYPE.exists("/test"));
}
TEST_CASE(TESTPRE "Files can be written and appended to",TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
{
File f = FSTYPE.open("config1.txt", "w");
REQUIRE(f);
f.println("file 1");
}
{
File f = FSTYPE.open("config1.txt", "a");
REQUIRE(f);
f.println("file 1 again");
}
{
File f = FSTYPE.open("config1.txt", "r");
REQUIRE(f);
char buf[128];
size_t len = f.read((uint8_t*)buf, sizeof(buf));
buf[len] = 0;
REQUIRE(strcmp(buf, "file 1\r\nfile 1 again\r\n") == 0);
}
}
TEST_CASE(TESTPRE "Files persist after reset", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("config1.txt", "file 1");
FS_MOCK_RESET();
REQUIRE(FSTYPE.begin());
REQUIRE(readFile("config1.txt") == "file 1");
}
TEST_CASE(TESTPRE "Filesystem is empty after format", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.format());
REQUIRE(FSTYPE.begin());
createFile("/1", "first");
createFile("/2", "second");
FSTYPE.end();
REQUIRE(FSTYPE.format());
REQUIRE(FSTYPE.begin());
Dir root = FSTYPE.openDir("/");
size_t count = 0;
while (root.next()) {
++count;
}
REQUIRE(count == 0);
}
TEST_CASE(TESTPRE "File names which are too long are rejected", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
const char* emptyName = "";
const char* longName_31 = "/234567890123456789012345678901";
const char* longName_32 = TOOLONGFILENAME;
REQUIRE_FALSE(FSTYPE.open(emptyName, "w"));
REQUIRE_FALSE(FSTYPE.open(emptyName, "r"));
REQUIRE_FALSE(FSTYPE.exists(emptyName));
REQUIRE_FALSE(FSTYPE.open(longName_32, "w"));
REQUIRE_FALSE(FSTYPE.open(longName_32, "r"));
REQUIRE_FALSE(FSTYPE.exists(longName_32));
REQUIRE(FSTYPE.open(longName_31, "w"));
REQUIRE(FSTYPE.open(longName_31, "r"));
REQUIRE(FSTYPE.exists(longName_31));
}
TEST_CASE(TESTPRE "#1685 Duplicate files", "[fs][bugreport]")
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/config", "some text");
createFile("/data", "");
readFile("/config");
createFile("/data", "more text");
auto files = listDir("/");
REQUIRE(files.size() == 2);
}
TEST_CASE(TESTPRE "#1819 Can list all files with openDir(\"\")", "[fs][bugreport]")
{
FS_MOCK_DECLARE(96, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/file1", "some text");
createFile("/file2", "other text");
createFile("file3", "more text");
createFile("sorta-dir/file4", "\n");
auto files = listDir("");
REQUIRE(files.size() == 4);
}
TEST_CASE(TESTPRE "truncate", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/file1", "some text");
auto f = FSTYPE.open("/file1", "r+");
REQUIRE(f.truncate(4));
f.close();
String s = readFile("/file1");
REQUIRE( s == "some" );
}
TEST_CASE(TESTPRE "open(w+) truncates file", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/file1", "some text");
String s = readFile("/file1");
REQUIRE( s == "some text");
auto f = FSTYPE.open("/file1", "w+");
f.close();
String t = readFile("/file1");
REQUIRE( t == "");
}
TEST_CASE(TESTPRE "peek() returns -1 on EOF", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/file1", "some text");
auto f = FSTYPE.open("/file1", "r+");
REQUIRE(f.seek(8));
REQUIRE(f.peek() == 't');
REQUIRE(f.read() == 't');
REQUIRE(f.peek() == -1);
REQUIRE(f.read() == -1);
f.close();
}
TEST_CASE(TESTPRE "seek() pase EOF returns error (#7323)", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/file1", "some text");
auto f = FSTYPE.open("/file1", "r+");
REQUIRE_FALSE(f.seek(10));
f.close();
}
TEST_CASE(TESTPRE "Rewriting file frees space immediately (#7426)", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
FSInfo inf;
FSTYPE.info(inf);
// Calculate the size to write per-FS, due to differing overheads
int kbToWrite = (inf.totalBytes - inf.usedBytes - 8192) / 1024;
// Create and overwrite a file >50% of spaceA (48/64K)
for (auto x = 0; x < 2; x++) {
auto f = FSTYPE.open("/file1.bin", "w");
REQUIRE(f);
uint8_t buff[1024];
memset(buff, 0xaa, 1024);
for (auto i = 0; i < kbToWrite; i++) {
REQUIRE(f.write(buff, 1024));
}
f.close();
FSTYPE.info(inf);
}
}
#if FSTYPE != SPIFFS
// Timestamp setter (#7682, #7775)
static time_t _my_time(void)
{
struct tm t;
bzero(&t, sizeof(t));
t.tm_year = 120;
t.tm_mon = 9;
t.tm_mday = 22;
t.tm_hour = 12;
t.tm_min = 13;
t.tm_sec = 14;
return mktime(&t);
}
TEST_CASE("Verify timeCallback works properly")
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
FSTYPE.setTimeCallback(_my_time);
File f = FSTYPE.open("/file.txt", "w");
f.write("Had we but world enough, and time,");
f.close();
time_t expected = _my_time();
f = FSTYPE.open("/file.txt", "r");
REQUIRE(abs(f.getCreationTime() - expected) < 60); // FAT has less precision in timestamp than time_t
REQUIRE(abs(f.getLastWrite() - expected) < 60); // FAT has less precision in timestamp than time_t
f.close();
}
#endif
#ifdef FS_HAS_DIRS
#if FSTYPE != SDFS
// We silently make subdirectories if they do not exist and silently remove
// them when they're no longer needed, so make sure we can clean up after
// ourselves. At some point we may drop this and go to normal POSIX mkdir
// behavior and expose the FS::mkdir() method, but for now this works OK.
TEST_CASE(TESTPRE "Removing all files in a subdir removes that subdir", TESTPAT)
{
FS_MOCK_DECLARE(128, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/empty", "");
createFile("/not_empty", "some text");
createFile("/another", "more text");
createFile("/subdir/empty", "");
createFile("/subdir/not_empty", "text again");
auto files = listDir("/");
REQUIRE(files.size() == 4);
files = listDir("/subdir");
REQUIRE(files.size() == 2);
// Delete one of subdir, should still exist afterwards
FSTYPE.remove("subdir/empty");
files = listDir("/subdir");
REQUIRE(files.size() == 1);
FSTYPE.remove("subdir/not_empty");
files = listDir("/subdir");
REQUIRE(files.size() == 0);
files = listDir("/");
REQUIRE(files.size() == 3);
REQUIRE(files.find("subdir") == std::end(files));
}
#endif
// LittleFS openDir is slightly different than SPIFFS. In SPIFFS there
// are no directories and "/" is just another character, so "/a/b/c" is a
// file in the root dir whose name is "/a/b/c". In LittleFS we have full
// directory support, so "/a/b/c" is a file "c" in the "/a/b" dir.
// This means that if you iterate over dirOpen("/") on SPIFFS you get
// a list of every file, including "subdirs". On LittleFS, you need to
// explicitly open the subdir to see its files. This behavior is the
// same as POSIX readdir(), and helps isolate subdirs from each other.
// Also note that the returned filenames in the "dir.next()" operator
// will be in that subdir (i.e. if you opendir("/a/b"); f=dir.next();"
// f.name == "c" and not "/a/b/c" as you would see in SPIFFS.
TEST_CASE(TESTPRE "Dir lists all files", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/empty", "");
createFile("/not_empty", "some text");
createFile("/another", "more text");
createFile("/subdir/empty", "");
createFile("/subdir/not_empty", "text again");
auto files = listDir("/");
REQUIRE(files.size() == 4);
bool empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
REQUIRE(empty);
bool not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
REQUIRE(not_empty);
bool another = (files.find("/another") != std::end(files)) || (files.find("another") != std::end(files));
REQUIRE(another);
files = listDir("/subdir");
REQUIRE(files.size() == 2);
bool sub_empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
REQUIRE(sub_empty);
bool sub_not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
REQUIRE(sub_not_empty);
}
File FindFileByName(const File f[], int count, const char *name)
{
for (int i=0; i<count; i++) {
if (!strcmp(name, f[i].name())) return f[i];
}
return f[0];
}
TEST_CASE(TESTPRE "Listfiles.ino example", TESTPAT)
{
FS_MOCK_DECLARE(128, 8, 512, "");
REQUIRE(FSTYPE.format());
REQUIRE(FSTYPE.begin());
createFile("file1", "hello");
createFile("file2", "hola");
createFile("dir1/file3", "nihao");
createFile("dir2/dir3/file4", "bonjour");
File root = FSTYPE.open("/", "r");
// LittleFS and SDFS are not guaranteed to put the names in order of creation, so
// manually look for them...
File f[4];
f[0] = root.openNextFile();
f[1] = root.openNextFile();
f[2] = root.openNextFile();
f[3] = root.openNextFile();
File file1 = FindFileByName(f, 4, "file1");
File file2 = FindFileByName(f, 4, "file2");
File dir1 = FindFileByName(f, 4, "dir1");
File dir1_file3 = dir1.openNextFile();
File dir2 = FindFileByName(f, 4, "dir2");
File dir2_dir3 = dir2.openNextFile();
File dir2_dir3_file4 = dir2_dir3.openNextFile();
bool ok;
ok = root.isDirectory() && !root.isFile() && !strcmp(root.name(), "/");
REQUIRE(ok);
ok = !file1.isDirectory() && file1.isFile() && !strcmp(file1.name(), "file1");
REQUIRE(ok);
ok = !file2.isDirectory() && file2.isFile() && !strcmp(file2.name(), "file2");
REQUIRE(ok);
ok = dir1.isDirectory() && !dir1.isFile() && !strcmp(dir1.name(), "dir1");
REQUIRE(ok);
ok = !dir1_file3.isDirectory() && dir1_file3.isFile() && !strcmp(dir1_file3.name(), "file3") &&
!strcmp(dir1_file3.fullName(), "dir1/file3");
REQUIRE(ok);
ok = dir2.isDirectory() && !dir2.isFile() && !strcmp(dir2.name(), "dir2");
REQUIRE(ok);
ok = dir2_dir3.isDirectory() && !dir2_dir3.isFile() && !strcmp(dir2_dir3.name(), "dir3");
REQUIRE(ok);
ok = !dir2_dir3_file4.isDirectory() && dir2_dir3_file4.isFile() && !strcmp(dir2_dir3_file4.name(), "file4") &&
!strcmp(dir2_dir3_file4.fullName(), "dir2/dir3/file4");
REQUIRE(ok);
REQUIRE(readFile("/file1") == "hello");
REQUIRE(readFile("file2") == "hola");
REQUIRE(readFile("dir1/file3") == "nihao");
REQUIRE(readFile("/dir2/dir3/file4") == "bonjour");
}
#else // !FS_HAS_DIRS
TEST_CASE(TESTPRE "Dir lists all files", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
createFile("/empty", "");
createFile("/not_empty", "some text");
createFile("/another", "more text");
createFile("/subdir/empty", "");
createFile("/subdir/not_empty", "text again");
auto files = listDir("/");
REQUIRE(files.size() == 5);
bool empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
REQUIRE(empty);
bool not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
REQUIRE(not_empty);
bool another = (files.find("/another") != std::end(files)) || (files.find("another") != std::end(files));
REQUIRE(another);
bool sub_empty = (files.find("/subdir/empty") != std::end(files)) || (files.find("subdir/empty") != std::end(files));
REQUIRE(sub_empty);
bool sub_not_empty = (files.find("/subdir/not_empty") != std::end(files)) || (files.find("subdir/not_empty") != std::end(files));
REQUIRE(sub_not_empty);
}
TEST_CASE(TESTPRE "Multisplendored File::writes", TESTPAT)
{
FS_MOCK_DECLARE(64, 8, 512, "");
REQUIRE(FSTYPE.begin());
File f = FSTYPE.open("/file.txt", "w");
f.write('a');
f.write(65);
f.write("bbcc");
f.write("theend", 6);
char block[3]={'x','y','z'};
f.write(block, 3);
uint32_t bigone = 0x40404040;
f.write((const uint8_t*)&bigone, 4);
f.close();
REQUIRE(readFile("/file.txt") == "aAbbcctheendxyz@@@@");
File g = FSTYPE.open("/file.txt", "w");
g.write(0);
g.close();
g = FSTYPE.open("/file.txt", "r");
uint8_t u = 0x66;
g.read(&u, 1);
g.close();
REQUIRE(u == 0);
}
#endif