diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 956ce7e..69ccdc4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,6 +57,21 @@ jobs: cmake --build build --config Release ' + - name: Test + if: matrix.name == 'linux-x64' + uses: tj-actions/docker-run@v2 + with: + image: cskburn-${{ matrix.name }}:latest + name: ${{ matrix.name }}-test + options: >- + --platform ${{ matrix.platform }} + --volume ${{ github.workspace }}:/cskburn + --workdir /cskburn + args: | + bash -c ' + ctest --test-dir build --output-on-failure + ' + - name: Upload artifacts uses: actions/upload-artifact@v4 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e213a4..7a534c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10) project(CSKBurnKit) -enable_testing() +include(CTest) # 未显式指定时默认 Release:Ninja 等单配置生成器下 --config 无效, # 否则发布产物会是无优化、带 assert 的默认构建 diff --git a/cskburn/CMakeLists.txt b/cskburn/CMakeLists.txt index ba0409a..dea8bf7 100644 --- a/cskburn/CMakeLists.txt +++ b/cskburn/CMakeLists.txt @@ -48,3 +48,13 @@ target_compile_options( ) install(TARGETS ${PROJECT_NAME} DESTINATION bin) + +if(BUILD_TESTING) + add_executable( + cskburn_utils_test + tests/test_utils.c + src/utils.c + ) + target_include_directories(cskburn_utils_test PRIVATE src) + add_test(NAME cskburn_utils COMMAND cskburn_utils_test) +endif() diff --git a/cskburn/src/utils.c b/cskburn/src/utils.c index 9d9fd80..615e647 100644 --- a/cskburn/src/utils.c +++ b/cskburn/src/utils.c @@ -1,5 +1,6 @@ #include "utils.h" +#include #include #include #include @@ -19,61 +20,102 @@ read_file(const char *path, uint8_t *buf, uint32_t limit) return (uint32_t)len; } -bool -scan_int(const char *str, uint32_t *out) +static bool +scan_int_prefix(const char *str, uint32_t *out, const char **end) { - if (str == NULL || str[0] == '\0' || str[0] == '-') { + if (str == NULL || out == NULL || end == NULL) { return false; } - // base 0 自动识别 0x/0X 十六进制前缀,其余按十进制解析 - char *end = NULL; + const char *first = str; + while (isspace((unsigned char)*first)) { + first++; + } + if (*first == '\0' || *first == '-') { + return false; + } + + const char *digits = *first == '+' ? first + 1 : first; + int base = digits[0] == '0' && (digits[1] == 'x' || digits[1] == 'X') ? 16 : 10; + + // 仅 0x/0X 前缀按十六进制解析,其余按十进制解析 + char *parsed_end = NULL; errno = 0; - unsigned long val = strtoul(str, &end, 0); - if (errno != 0 || end == str || *end != '\0' || val > UINT32_MAX) { + unsigned long val = strtoul(str, &parsed_end, base); + if (errno != 0 || parsed_end == str || val > UINT32_MAX) { return false; } *out = (uint32_t)val; + *end = parsed_end; return true; } bool -scan_addr_size(const char *str, uint32_t *addr, uint32_t *size) +scan_int(const char *str, uint32_t *out) { - char *split = strstr(str, ":"); - if (split == NULL) { + if (out == NULL) { return false; } - const char *str_addr = str; - const char *str_size = split + 1; + uint32_t value; + const char *end; + if (!scan_int_prefix(str, &value, &end) || *end != '\0') { + return false; + } - return scan_int(str_addr, addr) && scan_int(str_size, size); + *out = value; + return true; } bool -scan_addr_size_name(const char *str, uint32_t *addr, uint32_t *size, const char **name) +scan_addr_size(const char *str, uint32_t *addr, uint32_t *size) { - char *split; + if (str == NULL || addr == NULL || size == NULL) { + return false; + } - if (name == NULL) { + uint32_t parsed_addr; + uint32_t parsed_size; + const char *end; + + if (!scan_int_prefix(str, &parsed_addr, &end) || *end != ':') { + return false; + } + if (!scan_int(end + 1, &parsed_size)) { return false; } - if ((split = strstr(str, ":")) == NULL) { + *addr = parsed_addr; + *size = parsed_size; + return true; +} + +bool +scan_addr_size_name(const char *str, + uint32_t *addr, + uint32_t *size, + const char **name) +{ + if (str == NULL || addr == NULL || size == NULL || name == NULL) { return false; } - const char *str_addr = str; - const char *str_size = split + 1; + uint32_t parsed_addr; + uint32_t parsed_size; + const char *end; - if ((split = strstr(str_size, ":")) == NULL) { + if (!scan_int_prefix(str, &parsed_addr, &end) || *end != ':') { + return false; + } + if (!scan_int_prefix(end + 1, &parsed_size, &end) || *end != ':' || end[1] == '\0') { return false; } - *name = split + 1; - return scan_int(str_addr, addr) && scan_int(str_size, size); + *addr = parsed_addr; + *size = parsed_size; + *name = end + 1; + return true; } void diff --git a/cskburn/tests/test_utils.c b/cskburn/tests/test_utils.c new file mode 100644 index 0000000..73760e4 --- /dev/null +++ b/cskburn/tests/test_utils.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include + +#include "utils.h" + +#define CHECK(expr) \ + do { \ + if (!(expr)) { \ + fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, __LINE__, #expr); \ + return false; \ + } \ + } while (0) + +static bool +test_scan_int(void) +{ + uint32_t value = 0; + + CHECK(scan_int("0", &value) && value == 0); + CHECK(scan_int("4294967295", &value) && value == UINT32_MAX); + CHECK(scan_int("0xabcdef", &value) && value == 0xABCDEF); + CHECK(scan_int("0X100000", &value) && value == 0x100000); + CHECK(scan_int("010", &value) && value == 10); + CHECK(scan_int(" 123", &value) && value == 123); + + CHECK(!scan_int(NULL, &value)); + CHECK(!scan_int("1", NULL)); + CHECK(!scan_int("", &value)); + CHECK(!scan_int("-1", &value)); + CHECK(!scan_int(" -1", &value)); + CHECK(!scan_int("4294967296", &value)); + CHECK(!scan_int("0x", &value)); + CHECK(!scan_int("123junk", &value)); + CHECK(!scan_int("123:456", &value)); + + return true; +} + +static bool +test_scan_addr_size(void) +{ + uint32_t addr = 0; + uint32_t size = 0; + + CHECK(scan_addr_size("0x0:4194304", &addr, &size)); + CHECK(addr == 0 && size == 4194304); + CHECK(scan_addr_size("0X100000:0x100000", &addr, &size)); + CHECK(addr == 0x100000 && size == 0x100000); + CHECK(scan_addr_size("4096:8192", &addr, &size)); + CHECK(addr == 4096 && size == 8192); + CHECK(scan_addr_size("000010:000020", &addr, &size)); + CHECK(addr == 10 && size == 20); + + CHECK(!scan_addr_size(NULL, &addr, &size)); + CHECK(!scan_addr_size("1:2", NULL, &size)); + CHECK(!scan_addr_size("1:2", &addr, NULL)); + CHECK(!scan_addr_size("1", &addr, &size)); + CHECK(!scan_addr_size(":1", &addr, &size)); + CHECK(!scan_addr_size("1:", &addr, &size)); + CHECK(!scan_addr_size("1:2:3", &addr, &size)); + CHECK(!scan_addr_size("1:-2", &addr, &size)); + CHECK(!scan_addr_size("4294967296:1", &addr, &size)); + CHECK(!scan_addr_size("1:4294967296", &addr, &size)); + + addr = 11; + size = 22; + CHECK(!scan_addr_size("1:invalid", &addr, &size)); + CHECK(addr == 11 && size == 22); + + return true; +} + +static bool +test_scan_addr_size_name(void) +{ + uint32_t addr = 0; + uint32_t size = 0; + const char *name = NULL; + + CHECK(scan_addr_size_name("0x0:4194304:factory.bin", &addr, &size, &name)); + CHECK(addr == 0 && size == 4194304 && strcmp(name, "factory.bin") == 0); + CHECK(scan_addr_size_name("0X100000:0x6EC00:C:\\firmware\\dsp.bin", &addr, &size, + &name)); + CHECK(addr == 0x100000 && size == 0x6EC00); + CHECK(strcmp(name, "C:\\firmware\\dsp.bin") == 0); + CHECK(scan_addr_size_name("1:2:output:part.bin", &addr, &size, &name)); + CHECK(strcmp(name, "output:part.bin") == 0); + + char long_arg[700] = "0:1:"; + memset(long_arg + 4, 'a', sizeof(long_arg) - 5); + long_arg[sizeof(long_arg) - 1] = '\0'; + CHECK(scan_addr_size_name(long_arg, &addr, &size, &name)); + CHECK(name == long_arg + 4); + CHECK(strlen(name) == sizeof(long_arg) - 5); + + CHECK(!scan_addr_size_name(NULL, &addr, &size, &name)); + CHECK(!scan_addr_size_name("1:2:file", NULL, &size, &name)); + CHECK(!scan_addr_size_name("1:2:file", &addr, NULL, &name)); + CHECK(!scan_addr_size_name("1:2:file", &addr, &size, NULL)); + CHECK(!scan_addr_size_name("1:2", &addr, &size, &name)); + CHECK(!scan_addr_size_name(":2:file", &addr, &size, &name)); + CHECK(!scan_addr_size_name("1::file", &addr, &size, &name)); + CHECK(!scan_addr_size_name("1:2:", &addr, &size, &name)); + CHECK(!scan_addr_size_name("1:invalid:file", &addr, &size, &name)); + CHECK(!scan_addr_size_name("4294967296:1:file", &addr, &size, &name)); + + addr = 11; + size = 22; + name = "unchanged"; + CHECK(!scan_addr_size_name("1:invalid:file", &addr, &size, &name)); + CHECK(addr == 11 && size == 22 && strcmp(name, "unchanged") == 0); + + return true; +} + +int +main(void) +{ + if (!test_scan_int() || !test_scan_addr_size() || !test_scan_addr_size_name()) { + return 1; + } + puts("utils parser tests passed"); + return 0; +}