mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-12 22:29:00 -04:00
* fix(test): make the native-windows test suite build and run pio test -e native-windows failed every suite at the build stage. Five independent causes, all Windows-only: - TestUtil.cpp called lstat(), which MinGW-w64 does not provide. The state-checkpoint walk added in #11322 is fenced with ARCH_PORTDUINO, which native-windows also satisfies, so all 53 suites failed to compile. Route it through a stat() shim on _WIN32. - test_default, test_http_content_handler, test_meshpacket_serializer and test_serial define no setUp/tearDown and relied on the weak defaults PlatformIO emits in unity_config.c. GCC lowers a weak definition on PE-COFF to a weak external, leaving the symbol undefined, so it does not satisfy unity.c's reference and the link fails. Define them explicitly, as the other 49 suites already do. - test_mqtt included <arpa/inet.h>, absent on MinGW, for htonl(). Use winsock2.h there. - test_gps_update_scheduling uses TEST_ASSERT_DOUBLE_WITHIN. Unity omits double support unless UNITY_INCLUDE_DOUBLE is defined, so the assertion compiled to an unconditional failure. Define it for the env. - test_getfiles_rejects_overlong_path is excluded on _WIN32. Overrunning the 228-byte file_name needs at least 229 bytes below the portduino root, and that root is already ~34 bytes, so every qualifying path passes the 260-byte MAX_PATH: the nested mkdir() fails, the file is never created, and getFiles() has nothing to drop. No component layout satisfies both limits. Each of the seven suites that failed on Windows was verified individually after the change. test_fscommon_getfiles still fails in a full run, for a cause outside this change: rmDir() does not remove directories on Windows, so empty dirs left by an earlier run survive setUp() and make getFiles() report a depth truncation. That is a pre-existing FSCommon bug, reported separately. No Linux or macOS behaviour changes: every guard is _WIN32-only except UNITY_INCLUDE_DOUBLE, which is scoped to env:native-windows. * fix(test): define UNITY_INCLUDE_DOUBLE for every native env The flag was scoped to env:native-windows, but the gap is not Windows-specific. Verified on Debian with gcc against the Linux env's own Unity 2.6.1 and PlatformIO's generated native unity_config: UNITY_INCLUDE_DOUBLE : NOT defined UNITY_EXCLUDE_DOUBLE : defined test_double_within:FAIL: Unity Double Precision Disabled UNITY_INCLUDE_DOUBLE appears nowhere in the repo, the ini files, the workflow, or PlatformIO's unity runner, which adds only UNITY_INCLUDE_CONFIG_H. So TEST_ASSERT_DOUBLE_* is an always-failing stub on Linux and macOS too, not only on Windows. Moved to portduino_base.build_flags_common, which every native env resolves: native, native-tft, native-fb, native-tft-debug, coverage, coverage-event-policy, native-macos, native-windows and native-wasm. This does change Linux and macOS: TEST_ASSERT_DOUBLE_* becomes a real comparison instead of a stub. test_gps_update_scheduling is the only suite using those macros and its arithmetic is integer-based and bit-identical across platforms, so it should pass wherever it runs. Note it currently reports PASSED on CI in 0.03s while emitting no Unity output at all, so those assertions appear never to execute there; that is tracked separately and is not addressed here.
250 lines
8.3 KiB
C++
250 lines
8.3 KiB
C++
// Tests for getFiles() - the bounded file-manifest walk in src/FSCommon.cpp that PhoneAPI's
|
|
// STATE_SEND_FILEMANIFEST drives on every phone sync. Nothing else asserts its bounding behaviour:
|
|
// the walk does run unasserted from test_stream_api's handshakes, but no test covers the cap, the
|
|
// depth limit, the wasLimited paths, overlong-path rejection, or capacity release.
|
|
//
|
|
// These assert what the code does today. Today's code is already correct here - #10778 landed the
|
|
// by-reference collectFiles(), the 64-entry cap, strlcpy bounds and the swap-idiom release - so all
|
|
// of these describe present behaviour rather than a pending fix.
|
|
#include "MeshTypes.h"
|
|
#include "TestUtil.h"
|
|
// FSCommon.h is what defines FSCom, so it has to be included before anything tests for it.
|
|
#include "FSCommon.h"
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <unity.h>
|
|
#include <vector>
|
|
|
|
#ifdef FSCom
|
|
|
|
// The suite builds its tree through FSCom rather than host mkdir/fopen: PortduinoFS confines paths
|
|
// to its mountpoint, so going through the same API is the only way the test stays agnostic about
|
|
// where that mountpoint is.
|
|
static const char *kRoot = "/test_getfiles";
|
|
|
|
static void makeFile(const char *path, size_t bytes)
|
|
{
|
|
File f = FSCom.open(path, FILE_O_WRITE);
|
|
TEST_ASSERT_TRUE_MESSAGE(f, path);
|
|
for (size_t i = 0; i < bytes; i++)
|
|
f.write('x');
|
|
f.close();
|
|
}
|
|
|
|
static bool manifestContains(const std::vector<meshtastic_FileInfo> &files, const char *suffix)
|
|
{
|
|
for (const auto &f : files) {
|
|
const size_t nameLen = strlen(f.file_name);
|
|
const size_t suffixLen = strlen(suffix);
|
|
if (nameLen >= suffixLen && strcmp(f.file_name + nameLen - suffixLen, suffix) == 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void setUp(void)
|
|
{
|
|
rmDir(kRoot);
|
|
FSCom.mkdir(kRoot);
|
|
}
|
|
|
|
void tearDown(void)
|
|
{
|
|
// Leave nothing behind: the sandbox is per suite, but an undeclared write is still a finding.
|
|
rmDir(kRoot);
|
|
}
|
|
|
|
// 1. The cap is the fix from #10778 that this suite exists to pin.
|
|
void test_getfiles_respects_max_count(void)
|
|
{
|
|
for (int i = 0; i < 80; i++) {
|
|
char p[64];
|
|
snprintf(p, sizeof(p), "%s/f%02d.txt", kRoot, i);
|
|
makeFile(p, 4);
|
|
}
|
|
|
|
bool limited = false;
|
|
auto files = getFiles(kRoot, 1, 64, &limited);
|
|
TEST_ASSERT_EQUAL_size_t(64, files.size());
|
|
TEST_ASSERT_TRUE(limited);
|
|
}
|
|
|
|
// 2. wasLimited must not be sticky - it is an out-param, initialised per call.
|
|
void test_getfiles_unlimited_when_under_cap(void)
|
|
{
|
|
for (int i = 0; i < 5; i++) {
|
|
char p[64];
|
|
snprintf(p, sizeof(p), "%s/small%d.txt", kRoot, i);
|
|
makeFile(p, 4);
|
|
}
|
|
|
|
bool limited = true; // deliberately pre-set: the call must clear it
|
|
auto files = getFiles(kRoot, 1, 64, &limited);
|
|
TEST_ASSERT_EQUAL_size_t(5, files.size());
|
|
TEST_ASSERT_FALSE(limited);
|
|
}
|
|
|
|
// 3. Depth: a file below the requested level is absent AND reported as a truncation.
|
|
void test_getfiles_depth_limit(void)
|
|
{
|
|
char dir[128];
|
|
snprintf(dir, sizeof(dir), "%s/a", kRoot);
|
|
FSCom.mkdir(dir);
|
|
snprintf(dir, sizeof(dir), "%s/a/b", kRoot);
|
|
FSCom.mkdir(dir);
|
|
snprintf(dir, sizeof(dir), "%s/a/b/c", kRoot);
|
|
FSCom.mkdir(dir);
|
|
|
|
char deep[160];
|
|
snprintf(deep, sizeof(deep), "%s/a/b/c/deep.txt", kRoot);
|
|
makeFile(deep, 8);
|
|
|
|
bool shallowLimited = false;
|
|
auto shallow = getFiles(kRoot, 2, 64, &shallowLimited);
|
|
TEST_ASSERT_FALSE(manifestContains(shallow, "deep.txt"));
|
|
TEST_ASSERT_TRUE(shallowLimited);
|
|
|
|
bool deepLimited = false;
|
|
auto deepFiles = getFiles(kRoot, 4, 64, &deepLimited);
|
|
TEST_ASSERT_TRUE(manifestContains(deepFiles, "deep.txt"));
|
|
TEST_ASSERT_FALSE(deepLimited);
|
|
}
|
|
|
|
// 4. A path that will not fit meshtastic_FileInfo::file_name is dropped, not truncated into the
|
|
// manifest, and the drop is reported.
|
|
// Not built on Windows: any path long enough to overrun the 228-byte file_name also exceeds the
|
|
// 260-byte MAX_PATH, so the tree is never created and there is nothing to drop.
|
|
#ifndef _WIN32
|
|
void test_getfiles_rejects_overlong_path(void)
|
|
{
|
|
// file_name is 228 bytes; build a nested path that overruns it while each component stays
|
|
// inside the host's 255-byte limit.
|
|
char dir[512];
|
|
strcpy(dir, kRoot);
|
|
for (int level = 0; level < 3; level++) {
|
|
char component[80];
|
|
memset(component, 'd', sizeof(component) - 1);
|
|
component[sizeof(component) - 1] = '\0';
|
|
snprintf(dir + strlen(dir), sizeof(dir) - strlen(dir), "/%s", component);
|
|
FSCom.mkdir(dir);
|
|
}
|
|
|
|
char longFile[600];
|
|
snprintf(longFile, sizeof(longFile), "%s/overlong.txt", dir);
|
|
makeFile(longFile, 4);
|
|
|
|
char normal[128];
|
|
snprintf(normal, sizeof(normal), "%s/normal.txt", kRoot);
|
|
makeFile(normal, 4);
|
|
|
|
bool limited = false;
|
|
auto files = getFiles(kRoot, 5, 64, &limited);
|
|
TEST_ASSERT_TRUE(manifestContains(files, "normal.txt"));
|
|
TEST_ASSERT_FALSE(manifestContains(files, "overlong.txt"));
|
|
TEST_ASSERT_TRUE(limited);
|
|
|
|
// rmDir() cannot reach this tree - it walks through the same 228-byte path buffer that made the
|
|
// file unlistable in the first place - so unwind it here, deepest first.
|
|
FSCom.remove(longFile);
|
|
for (int level = 0; level < 3; level++) {
|
|
FSCom.rmdir(dir);
|
|
*strrchr(dir, '/') = '\0';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// 5. pathEndsWithDot() - no entry in the manifest may end in '.', which is how the walk filters the
|
|
// "." and ".." pseudo-entries some backends return.
|
|
void test_getfiles_skips_dot_entries(void)
|
|
{
|
|
char plain[128];
|
|
snprintf(plain, sizeof(plain), "%s/plain.txt", kRoot);
|
|
makeFile(plain, 4);
|
|
|
|
char trailingDot[128];
|
|
snprintf(trailingDot, sizeof(trailingDot), "%s/trailing.", kRoot);
|
|
makeFile(trailingDot, 4);
|
|
|
|
bool limited = false;
|
|
auto files = getFiles(kRoot, 1, 64, &limited);
|
|
TEST_ASSERT_TRUE(manifestContains(files, "plain.txt"));
|
|
for (const auto &f : files) {
|
|
const size_t len = strlen(f.file_name);
|
|
TEST_ASSERT_TRUE_MESSAGE(len == 0 || f.file_name[len - 1] != '.', f.file_name);
|
|
}
|
|
}
|
|
|
|
// 6. size_bytes is populated at all - the only coverage that the struct carries more than a name.
|
|
void test_getfiles_reports_sizes(void)
|
|
{
|
|
char p[128];
|
|
snprintf(p, sizeof(p), "%s/sized.txt", kRoot);
|
|
makeFile(p, 137);
|
|
|
|
bool limited = false;
|
|
auto files = getFiles(kRoot, 1, 64, &limited);
|
|
|
|
bool found = false;
|
|
for (const auto &f : files) {
|
|
if (strstr(f.file_name, "sized.txt")) {
|
|
TEST_ASSERT_EQUAL_UINT32(137, f.size_bytes);
|
|
found = true;
|
|
}
|
|
}
|
|
TEST_ASSERT_TRUE(found);
|
|
}
|
|
|
|
// 7. A directory that does not exist is empty and untruncated, not a crash.
|
|
void test_getfiles_missing_dir_is_empty(void)
|
|
{
|
|
bool limited = true;
|
|
auto files = getFiles("/test_getfiles_does_not_exist", 3, 64, &limited);
|
|
TEST_ASSERT_EQUAL_size_t(0, files.size());
|
|
TEST_ASSERT_FALSE(limited);
|
|
}
|
|
|
|
// 8. Capacity release. PhoneAPI's releaseFilesManifest() is file-local, so this pins the idiom it
|
|
// uses rather than calling it: a manifest reserved for 64 entries holds ~14 KB of file_name
|
|
// buffers, and clear() returns none of it. A size-only assertion would pass on clear(), which is
|
|
// exactly the bug #7924 shipped.
|
|
void test_release_files_manifest_frees_capacity(void)
|
|
{
|
|
std::vector<meshtastic_FileInfo> manifest;
|
|
manifest.reserve(64);
|
|
for (int i = 0; i < 64; i++) {
|
|
meshtastic_FileInfo info = {"", 0};
|
|
snprintf(info.file_name, sizeof(info.file_name), "/f%02d.txt", i);
|
|
manifest.push_back(info);
|
|
}
|
|
TEST_ASSERT_EQUAL_size_t(64, manifest.size());
|
|
TEST_ASSERT_GREATER_OR_EQUAL_size_t(64, manifest.capacity());
|
|
|
|
std::vector<meshtastic_FileInfo>().swap(manifest);
|
|
|
|
TEST_ASSERT_EQUAL_size_t(0, manifest.size());
|
|
TEST_ASSERT_EQUAL_size_t(0, manifest.capacity());
|
|
}
|
|
|
|
#endif // FSCom
|
|
|
|
void setup()
|
|
{
|
|
initializeTestEnvironment();
|
|
UNITY_BEGIN();
|
|
#ifdef FSCom
|
|
RUN_TEST(test_getfiles_respects_max_count);
|
|
RUN_TEST(test_getfiles_unlimited_when_under_cap);
|
|
RUN_TEST(test_getfiles_depth_limit);
|
|
#ifndef _WIN32
|
|
RUN_TEST(test_getfiles_rejects_overlong_path);
|
|
#endif
|
|
RUN_TEST(test_getfiles_skips_dot_entries);
|
|
RUN_TEST(test_getfiles_reports_sizes);
|
|
RUN_TEST(test_getfiles_missing_dir_is_empty);
|
|
RUN_TEST(test_release_files_manifest_frees_capacity);
|
|
#endif
|
|
exit(UNITY_END());
|
|
}
|
|
|
|
void loop() {}
|