diff --git a/.agent/rules/gemini.md b/.agent/rules/gemini.md new file mode 100644 index 0000000..edcf0c6 --- /dev/null +++ b/.agent/rules/gemini.md @@ -0,0 +1,10 @@ +--- +trigger: always_on +--- + +对RyanJson核心代码使用最严格的审查,其余代码审查可以不那么严格 +始终考虑嵌入式约束,有限的RAM和ROM,资源利用效率 +优化内存管理,保证高效和实时性 +关注模块解耦和可维护性 +检查 RTOS 环境下的线程安全问题 +代码风格一致性 \ No newline at end of file diff --git a/.clang-format b/.clang-format index 0802c4c..44e5f97 100644 --- a/.clang-format +++ b/.clang-format @@ -1,149 +1,185 @@ # SPDX-License-Identifier: Apache-2.0 # -# Note: The list of ForEachMacros can be obtained using: -# -# git grep -h '^#define [^[:space:]]*FOR_EACH[^[:space:]]*(' include/ \ -# | sed "s,^#define \([^[:space:]]*FOR_EACH[^[:space:]]*\)(.*$, - '\1'," \ -# | sort | uniq -# -# References: -# - https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# RyanJson clang-format 基线配置 +# 说明: +# - 注释尽量覆盖每个配置项,便于长期维护 +# - 以 LLVM 为基线,只覆盖项目明确约束 +# - 参考:https://clang.llvm.org/docs/ClangFormatStyleOptions.html --- -# 基于 LLVM 的代码风格作为起点,随后覆盖指定字段 +# 基础风格模板:以 LLVM 为起点 BasedOnStyle: LLVM -# 连续宏定义的对齐方式 -# Enabled: true -> 启用对齐连续宏定义 -# AcrossComments: true -> 跨注释也会对齐,适合一组宏中间穿插注释的情况 -AlignConsecutiveMacros: - Enabled: true - AcrossComments: true +# 单行最大宽度:超过后自动换行 +ColumnLimit: 140 -# 是否允许短代码块(如 { ... })出现在单行 -AllowShortBlocksOnASingleLine: true +# 基础缩进宽度(单位:列) +IndentWidth: 8 -# 是否允许短 case 标签单行 -# true -> 允许 `case X: doSomething();` -AllowShortCaseLabelsOnASingleLine: true +# Tab 显示宽度(单位:列) +TabWidth: 8 -# 是否允许短枚举在一行 -# true -> 允许短枚举如 `enum { A, B };` -AllowShortEnumsOnASingleLine: false +# 续行缩进宽度(函数参数折行、表达式折行等) +ContinuationIndentWidth: 8 -# 是否允许短函数在单行 -AllowShortFunctionsOnASingleLine: true +# 构造函数初始化列表缩进宽度(主要影响 C++,保留统一) +ConstructorInitializerIndentWidth: 8 -AllowShortCaseExpressionOnASingleLine: true +# Tab 使用策略:缩进和续行使用 Tab +UseTab: ForContinuationAndIndentation -# 短 if 语句单行显示策略 -# Always -> 允许并尽可能保留短 if 语句为单行(包括带 else 的情况) -# 你希望单行 + 大括号时使用这个选项 -AllowShortIfStatementsOnASingleLine: true +# 注释重排:false 表示不自动重排注释文本 +ReflowComments: false -# 是否允许短循环(for/while)单行显示 -AllowShortLoopsOnASingleLine: true +# 是否允许短代码块单行(如 { return; }) +AllowShortBlocksOnASingleLine: Always -# 属性宏列表,列出在格式化时应视为属性的宏(影响对齐、换行等) -# 如果代码库使用自定义属性宏,把它们列在这里可以提升格式化准确性 -AttributeMacros: - - __aligned - - __deprecated - - __packed - - __printf_like - - __syscall - - __syscall_always_inline - - __subsystem +# 是否允许短函数单行 +AllowShortFunctionsOnASingleLine: false -# 位字段冒号后的空格:After 表示 `int x : 3;` 中冒号后带一个空格(风格选择) -BitFieldColonSpacing: After +# 是否允许短 if 单行 +AllowShortIfStatementsOnASingleLine: WithoutElse + +# 是否允许短循环单行 +AllowShortLoopsOnASingleLine: false -# 大括号换行策略:使用 Custom 配合 BraceWrapping 指定细节 -# 你用了 Custom,这意味着下面的 BraceWrapping 字段决定具体行为 +# 是否允许短 case 标签单行 +AllowShortCaseLabelsOnASingleLine: true + +# 是否允许短 case 表达式单行 +AllowShortCaseExpressionOnASingleLine: true + +# 是否允许短枚举单行 +AllowShortEnumsOnASingleLine: false + +# 花括号总策略:自定义 BreakBeforeBraces: Custom + +# case 标签后的大括号是否换行 BraceWrapping: - AfterCaseLabel: false # case 标签后不另起行放 {,通常 case: 仍和语句对齐 - AfterClass: true # class 后大括号另起行 - AfterControlStatement: Always # 控制语句(if/for/while)后通常将 { 放在新行(可被覆盖) - AfterEnum: true # enum 后另起行 + AfterCaseLabel: false + + # class 后的大括号是否换行 + AfterClass: true + + # 控制语句(if/for/while)后的大括号换行策略 + AfterControlStatement: Always + + # enum 后的大括号是否换行 + AfterEnum: true + + # extern block 后的大括号是否换行 AfterExternBlock: false - AfterFunction: true # 函数体大括号另起行 + + # 函数定义后的大括号是否换行 + AfterFunction: true + + # namespace 后的大括号是否换行 AfterNamespace: true + + # ObjC 声明后的大括号是否换行 AfterObjCDeclaration: true + + # struct 后的大括号是否换行 AfterStruct: true + + # union 后的大括号是否换行 AfterUnion: false + + # catch 前是否换行 BeforeCatch: true + + # else 前是否换行 BeforeElse: true + + # lambda 体前是否换行 BeforeLambdaBody: false + + # do...while 中 while 前是否换行 BeforeWhile: false - IndentBraces: false # 不单独缩进大括号行 + + # 大括号行本身是否额外缩进 + IndentBraces: false + + # 空函数是否分裂成多行 SplitEmptyFunction: true + + # 空记录(如空 struct)是否分裂成多行 SplitEmptyRecord: true + + # 空命名空间是否分裂成多行 SplitEmptyNamespace: true -# 单行代码的最大列数(换行阈值) -ColumnLimit: 140 +# switch 内 case 标签是否额外缩进 +IndentCaseLabels: false -# 构造函数初始化列表的缩进宽度(可针对长列表调整可读性) -ConstructorInitializerIndentWidth: 8 +# goto 标签是否额外缩进 +IndentGotoLabels: false -# 折行缩进宽度(续行缩进) -ContinuationIndentWidth: 8 +# 是否强制插入花括号(单语句控制流也加花括号) +InsertBraces: true -# ForEach 宏列表:告诉 clang-format 哪些宏应当当作循环处理(便于格式化块体) -ForEachMacros: - - "ARRAY_FOR_EACH" - - "ARRAY_FOR_EACH_PTR" - - "FOR_EACH" +# 文件末尾是否补换行 +InsertNewlineAtEOF: true -# If 宏列表:把 CHECKIF 等宏视为 if 语句(影响括号和后续块处理) -IfMacros: - - "CHECKIF" +# 位域冒号前后空格策略 +BitFieldColonSpacing: After + +# 控制语句括号前空格策略(if (x)) +SpaceBeforeParens: ControlStatementsExceptControlMacros + +# 继承冒号前空格策略(主要影响 C++) +SpaceBeforeInheritanceColon: false -# include 文件的分类和排序优先级 -# Regex: 正则匹配,Priority: 数字越小优先级越高(越先放) +# 连续宏定义对齐策略 +AlignConsecutiveMacros: + # 是否启用连续宏对齐 + Enabled: true + + # 是否跨注释继续对齐 + AcrossComments: true + +# include 是否自动排序(Never 表示保持人工顺序) +SortIncludes: Never + +# include 分类规则(数值越小优先级越高) IncludeCategories: + # 项目内双引号头文件 - Regex: '^".*\.h"$' Priority: 0 + + # C 标准库头文件 - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|limits|locale|math|setjmp|signal|stdarg|stdbool|stddef|stdint|stdio|stdlib|string|tgmath|time|wchar|wctype)\.h>$' Priority: 1 - - Regex: '^\$' + + # Ryan 体系头文件 + - Regex: '^$' Priority: 2 + + # 兜底分类 - Regex: ".*" Priority: 3 -# case 标签是否缩进(true 会将 case 缩进到 switch 中) -# false -> case 与 switch 对齐(你原先设置 false) -IndentCaseLabels: false - -# goto 标签是否缩进(false 表示标签在行首) -IndentGotoLabels: false - -# 缩进宽度(通常与制表符策略配合使用) -IndentWidth: 8 - -# 自动插入大括号(即使单语句也插入 { }) -# 这可以避免单行语句因为后续添加语句而引入 bug -InsertBraces: true - -# 文件末尾自动插入换行 -InsertNewlineAtEOF: true - -# 继承冒号前是否加空格(False 表示不加空格:"class A: public B") -SpaceBeforeInheritanceColon: False - -# 控制语句后是否加空格(这个值控制 if/for/while 等的格式) -# ControlStatementsExceptControlMacros -> 控制语句(非宏)前加空格:`if (cond)` 而非 `if(cond)` -SpaceBeforeParens: ControlStatementsExceptControlMacros +# 视为 foreach 语义的宏列表(用于正确缩进与换行) +ForEachMacros: + - RyanJsonArrayForEach + - RyanJsonObjectForEach -# 包含文件是否自动排序(Never 表示不排序) -SortIncludes: Never +# 视为 if 语义的宏列表 +IfMacros: + - CHECKIF -# 缩进与续行使用制表符策略 -# ForContinuationAndIndentation -> 续行与缩进使用制表符,其他空格仍按规则 -UseTab: ForContinuationAndIndentation +# 视为属性的宏列表(影响断行与对齐) +AttributeMacros: + - __aligned + - __deprecated + - __packed + - __printf_like + - __syscall + - __syscall_always_inline + - __subsystem -# 对空白敏感的宏列表(多用于预处理器宏展开格式保持) +# 空白敏感宏:保持参数空格布局,避免被格式化破坏 WhitespaceSensitiveMacros: - COND_CODE_0 - COND_CODE_1 @@ -153,11 +189,3 @@ WhitespaceSensitiveMacros: - STRINGIFY - Z_STRINGIFY - DT_FOREACH_PROP_ELEM_SEP - -# -------------------------- -# 可选:降低 clang-format 拆行惩罚,使其更倾向于保留短 if/else 单行 -# 下面两个值可以帮助把格式化后的多行 if/else 更可能压缩成单行(仅在 AllowShortIfStatementsOnASingleLine: Always 有效时可用) -# PenaltyBreakIfElse: 0 -# PenaltyBreakStatement: 0 - -# 注:上面的 Penalty 设置是可选的,如果你发现 clang-format 依旧不把某些 if/else 压成单行,可以取消注释并试验效果。 diff --git a/.clang-format-ignore b/.clang-format-ignore index d34dec8..c42773c 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -1,2 +1,10 @@ -# 忽略外部包 -externalModule/* \ No newline at end of file +# 第三方代码:不做本仓风格重排,避免升级/同步时产生大 diff +/test/externalModule/** + +# 生成目录:格式化无意义 +/build/** +/.xmake/** +/coverage/** + +# Fuzz 语料:不是源码 +/test/fuzzer/corpus/** diff --git a/.clang-tidy b/.clang-tidy index e240bd8..4876392 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -27,8 +27,9 @@ # Checks: '-*,clang-diagnostic-*,llvm-*,misc-*,-misc-const-correctness,-misc-unused-parameters,-misc-non-private-member-variables-in-classes,-misc-no-recursion,-misc-use-anonymous-namespace,readability-identifier-naming,-misc-include-cleaner' Checks: - - "-*,clang-diagnostic-*,clang-analyzer-*,llvm-*,concurrency-*,performance-*,cert-*" + - "-*,clang-diagnostic-*,clang-analyzer-*,concurrency-*,performance-*,cert-*" # 静默的窄化转换交给编译器来判断? + - 'llvm-*,-llvm-include-order' - 'bugprone-*,-bugprone-easily-swappable-parameters' - 'readability-identifier-naming' - 'misc-*,-misc-const-correctness,-misc-no-recursion,-misc-include-cleaner' diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..1d57af3 --- /dev/null +++ b/.clangd @@ -0,0 +1,10 @@ +Index: + Background: Build # 开启后台索引 + +# 如果你的 compile_commands.json 不在根目录,需要在这里显式指定 +CompileFlags: + CompilationDatabase: ".vscode" + +Diagnostics: + UnusedIncludes: None # 关键:这会关闭“未使用头文件”的提示 + MissingIncludes: None # 可选:关闭“缺失头文件”的提示 diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml new file mode 100644 index 0000000..3836626 --- /dev/null +++ b/.github/workflows/ci-pr.yml @@ -0,0 +1,99 @@ +name: ci-pr + +on: + # PR 触发:用于主分支合并前的快速回归 + pull_request: + branches: + - main + - master + # 手动触发:便于在分支上临时复跑 + workflow_dispatch: + +permissions: + contents: read + +jobs: + unitFull: + name: unit-full + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: 拉取代码 + uses: actions/checkout@v4 + + - name: 安装 xmake + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + + - name: 安装 clang/llvm(覆盖率工具) + run: | + sudo apt-get update + sudo apt-get install -y clang llvm + + - name: 单元测试 full 模式(8 组配置全覆盖,跳过覆盖率) + run: | + chmod +x ./scripts/ci/runBaseCoverage.sh + UNIT_MODE=full \ + UNIT_SKIP_COV=1 \ + UNIT_STOP_ON_FAIL=1 \ + bash ./scripts/ci/runBaseCoverage.sh + + # 即使 full 模式跳过覆盖率,也保留执行产物,便于失败排查 + - name: 上传单测产物 + if: always() + uses: actions/upload-artifact@v4 + with: + name: unit-full-artifacts + path: | + coverage/ + build/ + if-no-files-found: ignore + retention-days: 7 + + fuzzQuick: + name: fuzz-quick-default + runs-on: ubuntu-latest + timeout-minutes: 35 + + steps: + - name: 拉取代码 + uses: actions/checkout@v4 + + - name: 安装 xmake + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + + - name: 安装 clang/llvm(fuzzer + 覆盖率工具) + run: | + sudo apt-get update + sudo apt-get install -y clang llvm + + - name: Fuzz quick 模式(跳过覆盖率) + env: + # PR 只跑一组默认语义,优先保证反馈速度 + RYANJSON_STRICT_OBJECT_KEY_CHECK: "false" + RYANJSON_DEFAULT_ADD_AT_HEAD: "true" + RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC: "true" + run: | + chmod +x ./scripts/ci/runCoverage.sh + FUZZ_MODE=quick \ + FUZZ_SKIP_COV=1 \ + FUZZ_MAX_TOTAL_TIME=45 \ + FUZZ_WORKERS=2 \ + FUZZ_JOBS=2 \ + bash ./scripts/ci/runCoverage.sh + + - name: 上传 fuzz 产物 + if: always() + uses: actions/upload-artifact@v4 + with: + name: fuzz-quick-default-artifacts + path: | + coverage/ + build/ + *.log + if-no-files-found: ignore + retention-days: 7 diff --git a/.github/workflows/nightly-fuzz.yml b/.github/workflows/nightly-fuzz.yml new file mode 100644 index 0000000..a315e00 --- /dev/null +++ b/.github/workflows/nightly-fuzz.yml @@ -0,0 +1,336 @@ +name: nightly-fuzz + +on: + # 每天夜间巡检一次(UTC 时间) + schedule: + - cron: "0 18 * * *" + # 手动触发:既能跑 nightly,也能跑 full(替代原 release 工作流) + workflow_dispatch: + inputs: + unitSkipCov: + description: "单测是否跳过覆盖率(true/false)" + required: true + default: false + type: boolean + fuzzProfile: + description: "fuzz 档位(nightly/full)" + required: true + default: "nightly" + type: choice + options: + - nightly + - full + fuzzSkipCov: + description: "fuzz 是否跳过覆盖率(true/false)" + required: true + default: true + type: boolean + fuzzMaxTotalTime: + description: "fuzz 每个 job 总时长预算(秒)" + required: true + default: "300" + type: string + fuzzWorkers: + description: "fuzz workers" + required: true + default: "4" + type: string + fuzzJobs: + description: "fuzz jobs" + required: true + default: "4" + type: string + runFuzzCoverageBaseline: + description: "nightly 档是否额外跑 1 组覆盖率基线" + required: true + default: true + type: boolean + +permissions: + contents: read + +jobs: + unitSuite: + name: unit-suite + runs-on: ubuntu-latest + timeout-minutes: 120 + + steps: + - name: 拉取代码 + uses: actions/checkout@v4 + + - name: 安装 xmake + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + + - name: 安装 clang/llvm(覆盖率工具) + run: | + sudo apt-get update + sudo apt-get install -y clang llvm + + - name: 执行单元测试 full 矩阵(8 组配置全覆盖) + run: | + chmod +x ./scripts/ci/runBaseCoverage.sh + + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + unitMode="full" + if [[ "${{ inputs.unitSkipCov }}" == "true" ]]; then + unitSkipCov=1 + else + unitSkipCov=0 + fi + else + # schedule 固定跑 full,确保 8 组配置都覆盖 + unitMode="full" + unitSkipCov=0 + fi + + UNIT_MODE="${unitMode}" \ + UNIT_SKIP_COV="${unitSkipCov}" \ + UNIT_STOP_ON_FAIL=0 \ + bash ./scripts/ci/runBaseCoverage.sh + + - name: 上传单测产物 + if: always() + uses: actions/upload-artifact@v4 + with: + name: unit-suite-artifacts + path: | + coverage/ + build/ + if-no-files-found: ignore + retention-days: 14 + + fuzzNightlyMatrix: + name: fuzz-nightly-${{ matrix.caseId }} + if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.fuzzProfile == 'nightly') }} + runs-on: ubuntu-latest + timeout-minutes: 100 + strategy: + fail-fast: false + matrix: + include: + # nightly:重点覆盖 strict × addAtHead 的四种组合 + # scientific 固定为 true,减少组合爆炸 + - caseId: s0h0 + strict: "false" + head: "false" + scientific: "true" + - caseId: s0h1 + strict: "false" + head: "true" + scientific: "true" + - caseId: s1h0 + strict: "true" + head: "false" + scientific: "true" + - caseId: s1h1 + strict: "true" + head: "true" + scientific: "true" + + steps: + - name: 拉取代码 + uses: actions/checkout@v4 + + - name: 安装 xmake + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + + - name: 安装 clang/llvm + run: | + sudo apt-get update + sudo apt-get install -y clang llvm + + - name: 执行 nightly fuzz 矩阵 + env: + RYANJSON_STRICT_OBJECT_KEY_CHECK: ${{ matrix.strict }} + RYANJSON_DEFAULT_ADD_AT_HEAD: ${{ matrix.head }} + RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC: ${{ matrix.scientific }} + run: | + chmod +x ./scripts/ci/runCoverage.sh + + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + if [[ "${{ inputs.fuzzSkipCov }}" == "true" ]]; then + fuzzSkipCov=1 + else + fuzzSkipCov=0 + fi + fuzzMaxTotalTime="${{ inputs.fuzzMaxTotalTime }}" + fuzzWorkers="${{ inputs.fuzzWorkers }}" + fuzzJobs="${{ inputs.fuzzJobs }}" + else + # schedule 固定 nightly 默认预算 + fuzzSkipCov=1 + fuzzMaxTotalTime=240 + fuzzWorkers=4 + fuzzJobs=4 + fi + + FUZZ_MODE=nightly \ + FUZZ_SKIP_COV="${fuzzSkipCov}" \ + FUZZ_MAX_TOTAL_TIME="${fuzzMaxTotalTime}" \ + FUZZ_WORKERS="${fuzzWorkers}" \ + FUZZ_JOBS="${fuzzJobs}" \ + bash ./scripts/ci/runCoverage.sh + + - name: 上传 nightly fuzz 产物 + if: always() + uses: actions/upload-artifact@v4 + with: + name: fuzz-nightly-${{ matrix.caseId }}-artifacts + path: | + coverage/ + build/ + *.log + if-no-files-found: ignore + retention-days: 14 + + fuzzNightlyCoverage: + name: fuzz-nightly-coverage-baseline + if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.fuzzProfile == 'nightly' && inputs.runFuzzCoverageBaseline) }} + runs-on: ubuntu-latest + timeout-minutes: 100 + + steps: + - name: 拉取代码 + uses: actions/checkout@v4 + + - name: 安装 xmake + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + + - name: 安装 clang/llvm + run: | + sudo apt-get update + sudo apt-get install -y clang llvm + + - name: 执行 nightly 覆盖率基线 + env: + RYANJSON_STRICT_OBJECT_KEY_CHECK: "false" + RYANJSON_DEFAULT_ADD_AT_HEAD: "true" + RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC: "true" + run: | + chmod +x ./scripts/ci/runCoverage.sh + + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + fuzzMaxTotalTime="${{ inputs.fuzzMaxTotalTime }}" + fuzzWorkers="${{ inputs.fuzzWorkers }}" + fuzzJobs="${{ inputs.fuzzJobs }}" + else + fuzzMaxTotalTime=300 + fuzzWorkers=4 + fuzzJobs=4 + fi + + FUZZ_MODE=nightly \ + FUZZ_SKIP_COV=0 \ + FUZZ_MAX_TOTAL_TIME="${fuzzMaxTotalTime}" \ + FUZZ_WORKERS="${fuzzWorkers}" \ + FUZZ_JOBS="${fuzzJobs}" \ + bash ./scripts/ci/runCoverage.sh + + - name: 上传 nightly 覆盖率基线产物 + if: always() + uses: actions/upload-artifact@v4 + with: + name: fuzz-nightly-coverage-artifacts + path: | + coverage/ + build/ + *.log + if-no-files-found: ignore + retention-days: 14 + + fuzzFullMatrix: + name: fuzz-full-${{ matrix.caseId }} + if: ${{ github.event_name == 'workflow_dispatch' && inputs.fuzzProfile == 'full' }} + runs-on: ubuntu-latest + timeout-minutes: 180 + strategy: + fail-fast: false + matrix: + include: + # full:三个布尔宏全组合(2 × 2 × 2 = 8) + - caseId: s0h0c0 + strict: "false" + head: "false" + scientific: "false" + - caseId: s0h0c1 + strict: "false" + head: "false" + scientific: "true" + - caseId: s0h1c0 + strict: "false" + head: "true" + scientific: "false" + - caseId: s0h1c1 + strict: "false" + head: "true" + scientific: "true" + - caseId: s1h0c0 + strict: "true" + head: "false" + scientific: "false" + - caseId: s1h0c1 + strict: "true" + head: "false" + scientific: "true" + - caseId: s1h1c0 + strict: "true" + head: "true" + scientific: "false" + - caseId: s1h1c1 + strict: "true" + head: "true" + scientific: "true" + + steps: + - name: 拉取代码 + uses: actions/checkout@v4 + + - name: 安装 xmake + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + + - name: 安装 clang/llvm + run: | + sudo apt-get update + sudo apt-get install -y clang llvm + + - name: 执行 full fuzz 矩阵 + env: + RYANJSON_STRICT_OBJECT_KEY_CHECK: ${{ matrix.strict }} + RYANJSON_DEFAULT_ADD_AT_HEAD: ${{ matrix.head }} + RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC: ${{ matrix.scientific }} + run: | + chmod +x ./scripts/ci/runCoverage.sh + + if [[ "${{ inputs.fuzzSkipCov }}" == "true" ]]; then + fuzzSkipCov=1 + else + fuzzSkipCov=0 + fi + + FUZZ_MODE=full \ + FUZZ_SKIP_COV="${fuzzSkipCov}" \ + FUZZ_MAX_TOTAL_TIME="${{ inputs.fuzzMaxTotalTime }}" \ + FUZZ_WORKERS="${{ inputs.fuzzWorkers }}" \ + FUZZ_JOBS="${{ inputs.fuzzJobs }}" \ + bash ./scripts/ci/runCoverage.sh + + - name: 上传 full fuzz 产物 + if: always() + uses: actions/upload-artifact@v4 + with: + name: fuzz-full-${{ matrix.caseId }}-artifacts + path: | + coverage/ + build/ + *.log + if-no-files-found: ignore + retention-days: 30 diff --git a/.gitignore b/.gitignore index e140c66..c432bbd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ null *.exe # 忽略 -core build .xmake .dump @@ -19,6 +18,6 @@ build default.profdata default.profraw test/fuzzer/corpus -docs -fuzz-* \ No newline at end of file +fuzz-* +RyanJson_Technical_Paper.md \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index d6a342b..94ab47c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,31 @@ { + "Lua.codeLens.enable": false, + "Lua.hint.enable": false, + "Lua.completion.enable": false, + "Lua.format.enable": false, + "Lua.hover.enable": false, + "Lua.diagnostics.enable": false, + "Lua.semantic.enable": false, + "Lua.addonManager.enable": false, + "Lua.signatureHelp.enable": false, "clangd.enable": false, - "C_Cpp.intelliSenseEngine": "default", - "C_Cpp.codeAnalysis.clangTidy.enabled": true, - "c-cpp-flylint.enable": true, + "clangd.arguments": [], + "liveServer.settings.file": "${workspaceFolder}/coverage/**", + "liveServer.settings.ignoreFiles": [ + "**", // 第一步:先忽略所有文件(简单粗暴) + "!coverage/**" // 第二步:用感叹号 ! 把 coverage 目录“救”回来 + ], + // "C_Cpp.intelliSenseEngine": "disabled", + // "C_Cpp.errorSquiggles": "disabled", // 关闭微软的波浪线 + // "C_Cpp.autocomplete": "disabled", // 关闭微软的自动补全 + "C_Cpp.default.compileCommands": "${workspaceFolder}/.vscode/compile_commands.json", + "C_Cpp.codeAnalysis.clangTidy.enabled": false, + "C_Cpp.codeAnalysis.clangTidy.args": [ + // "--extra-arg=-ferror-limit=1" + // "--extra-arg=-m32", + "--extra-arg=--target=arm-none-eabi-gcc" + ], + "c-cpp-flylint.enable": false, "c-cpp-flylint.cppcheck.severityLevels": { "error": "Error", "warning": "Warning", @@ -12,49 +35,41 @@ "information": "Information" }, "c-cpp-flylint.cppcheck.extraArgs": [ - // "--suppress=constParameterPointer", - // "--suppress=constParameterCallback", + "--suppress=constParameterPointer", + "--suppress=constParameterCallback", "--check-level=exhaustive", - // "--suppress=variableScope", - // "--suppress=unreadVariable", - // "--suppress=constVariablePointer", - // "--suppress=constParameter", + "--suppress=variableScope", + "--suppress=unreadVariable", + "--suppress=constVariablePointer", + "--suppress=constParameter", + "--suppress=unusedStructMember", ], - "files.watcherExclude": { - "**/test/fuzzer/corpus/**": true, - "./docs": true, - "./build": true, - "./.xmake": true, - }, - "files.exclude": { - "**/test/fuzzer/corpus/**": true, - "./docs": true, - "./build": true, - "./.xmake": true, - }, + "makefile.configureOnOpen": false, + "liveServer.settings.port": 5501, "files.associations": { - "*.c": "c", - "inttypes.h": "c", - "float.h": "c", - "stdlib.h": "c", - "limits.h": "c", - "stdio.h": "c", - "stdint.h": "c", - "ryanjsontest.h": "c", - "dirent.h": "c", - "valloc.h": "c", - "initializer_list": "c", - "array": "c", - "string_view": "c", - "utility": "c", - "math.h": "c", - "compare": "c", - "type_traits": "c", - "cjson.h": "c", - "ryanjson.h": "c", + "*.rules": "makefile", + "*.vars": "makefile", + "optional": "cpp", + "istream": "cpp", + "ostream": "cpp", + "system_error": "cpp", + "array": "cpp", + "functional": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "string": "cpp", + "deque": "cpp", + "vector": "cpp", + "iterator": "cpp", + "string_view": "cpp", "string.h": "c", - "stdarg.h": "c", - "cstdlib": "c", - "ryanjsonconfig.h": "c" - }, + "testbase.h": "c", + "ryanjson.h": "c", + "chrono": "c", + "random": "c", + "limits": "c", + "algorithm": "c", + "time.h": "c" + } } \ No newline at end of file diff --git a/Makefile b/Makefile index d13e7be..dff8014 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,46 @@ -CFLAGS_INC = -I RyanJson -CFLAGS_INC += -I cJSON -CFLAGS_INC += -I yyjson -CFLAGS_INC += -I RyanJsonExample/valloc -CFLAGS_INC += -I RyanJsonExample +# 编译器设置 +CC = gcc +C_FLAGS = -std=gnu99 -O2 -Wall -Wextra -Wno-unused-parameter +C_FLAGS += -DRyanJsonProjectRootPath=\"$(shell pwd)\" +# 头文件包含目录 +CFLAGS_INC = -I ./RyanJson +CFLAGS_INC += -I ./example +CFLAGS_INC += -I ./test +CFLAGS_INC += -I ./test/baseTest +CFLAGS_INC += -I ./test/externalModule/valloc +CFLAGS_INC += -I ./test/externalModule/tlsf +CFLAGS_INC += -I ./test/externalModule/cJSON +CFLAGS_INC += -I ./test/externalModule/yyjson +# 源文件扫描 (排除 fuzzer) src = $(wildcard ./RyanJson/*.c) -src += $(wildcard ./cJSON/*.c) -src += $(wildcard ./yyjson/*.c) -src += $(wildcard ./RyanJsonExample/valloc/*.c) -src += $(wildcard ./RyanJsonExample/*.c) +src += $(wildcard ./example/*.c) +src += $(wildcard ./test/*.c) +src += $(wildcard ./test/baseTest/*.c) +src += $(wildcard ./test/externalModule/valloc/*.c) +src += $(wildcard ./test/externalModule/tlsf/*.c) +src += $(wildcard ./test/externalModule/cJSON/*.c) +src += $(wildcard ./test/externalModule/yyjson/*.c) -obj = $(patsubst %.c, %.o, $(src)) -target = app.o -CC = gcc -C_FLAGS = -Wall -Wextra -Wno-unused-parameter -Wformat=2 +# 中间对象 +obj = $(src:.c=.o) + +# 目标程序 - 修改名字避免与源码文件夹 RyanJson 重名 +target = app + +# 默认规则 +all: $(target) $(target): $(obj) - $(CC) $(CFLAGS_INC) $(obj) $(C_FLAGS) -o $(target) -lm + $(CC) $(obj) $(C_FLAGS) -o $(target) -lm +# 编译模式规则 %.o: %.c - $(CC) $(CFLAGS_INC) $(C_FLAGS) -c $< -o $@ -lm + $(CC) $(CFLAGS_INC) $(C_FLAGS) -c $< -o $@ +# 清理规则 .PHONY: clean clean: - rm -rf $(obj) $(target) + rm -f $(obj) $(target) diff --git a/README.md b/README.md index 795eb5f..d38385a 100644 --- a/README.md +++ b/README.md @@ -9,26 +9,24 @@ ### 1、介绍 -**RyanJson** 是一个小巧的 C 语言 JSON 解析器,支持 JSON 文本的解析与生成,针对嵌入式平台的 **低内存占用** 进行了深度优化。 +**RyanJson** 是一个针对嵌入式平台深度优化的 C 语言 JSON 解析器。它在保持代码健壮性的同时,实现了极致的内存控制,旨在解决 cJSON 等传统库在复杂场景下内存占用过高的问题。 *初衷:项目重构后 JSON 结构复杂度提升,cJSON 内存占用过高,无法满足嵌入式场景需求。* #### ✅ 特性亮点 -- 💡 **极致内存优化**:在资源受限设备上实现 **40-70% 内存节省**(对比 cJSON),在 RT-Thread 平台 malloc 头部空间为 12 字节时节省约 40%,无 malloc 头部空间可接近 70%。同时保持工业级健壮性,运行速度与 cJSON 基本持平。 -- 🔍 **模糊测试保障**:基于[LLVM Fuzzer](https://llvm.org/docs/LibFuzzer.html) ,上亿级数据输入分支覆盖率 **99.9%**,确保在各种非法输入和极端场景下依旧安全。**[点击在线查看覆盖率信息](https://ryan-cw-code.github.io/RyanJson/)** -- 🛡️ **运行时安全分析验证**,使用 **[Sanitizer](https://clang.llvm.org/docs/index.html#sanitizers)** 系列工具,捕获内存越界、Use-after-free、数据竞争、未定义行为、内存泄漏等问题,提升代码健壮性与安全性 -- 📐**高质量代码保障** , 引入 **[clang-tidy](https://clang.llvm.org/extra/clang-tidy/#clang-tidy)** 与 **[Cppcheck](https://cppcheck.sourceforge.io/)** 进行静态分析,实现接近语法级的"**零缺陷**",显著提升可维护性与可读性 -- 🤖 **AI 辅助开发与审查**,结合 **[coderabbitai](https://www.coderabbit.ai)** 、 **[Copilot](https://github.com/features/copilot)** 、**[Gemini Code Assist](https://codeassist.google/)**,在编码与代码审查阶段持续优化代码质量,构建多层安全防线 -- 🧪 **9 大类专项测试用例**,覆盖广泛场景,全链路内存泄漏检测,强化稳定性与可靠性 -- ⚙️ **低内存占用**:动态内存扩展方案,内存空间利用率高。 -- 👩‍💻 **开发者友好**:轻松集成,类 cJSON API,迁移成本低。 -- 📜 **严格但不严苛**:符合 RFC 8259 绝大部分JSON标准,支持无限的Json嵌套级别(需注意堆栈空间)、灵活的配置修改项 -- 🔧 **可扩展性**:允许注释(需调用mini函数清除注释后再解析)、尾随逗号等无效字符(parse时可配置是否允许)等 +- 💡 **极致内存优化:** 通过动态内存扩展与紧凑结构设计,相比 cJSON 减少 **50% 左右的内存占用**。 +- 🔍 **模糊测试保障:** 基于[LLVM Fuzzer](https://llvm.org/docs/LibFuzzer.html) 生成上亿级测试用例,**分支覆盖率 100%**,确保非法输入和极端场景下依旧安全。**[点击在线查看覆盖率信息](https://ryan-cw-code.github.io/RyanJson/)** +- 🧪 **9 大类专项测试用例:** 覆盖广泛场景,全链路内存泄漏检测,强化稳定性与可靠性 +- 🛡️ **运行时安全分析验证:** 使用 **[Sanitizer](https://clang.llvm.org/docs/index.html#sanitizers)** 系列工具,捕获内存越界、Use-after-free、数据竞争、未定义行为、内存泄漏等问题,提升代码健壮性与安全性 +- 📐**高质量代码保障:** 引入 **[clang-tidy](https://clang.llvm.org/extra/clang-tidy/#clang-tidy)** 与 **[Cppcheck](https://cppcheck.sourceforge.io/)** 进行静态分析,代码质量接近语法级的"**零缺陷**" +- 🤖 **AI 辅助开发与审查:** 结合 **[Gemini Code Assist](https://codeassist.google/)** 、**[coderabbitai](https://www.coderabbit.ai)** 、 **[Copilot](https://github.com/features/copilot)** ,在编码与代码审查阶段持续优化代码质量,构建多层安全防线 +- 👩‍💻 **开发者友好:** 类 cJSON 接口设计,迁移成本低 +- 📜 **严格但不严苛:** 符合 **[RFC 8259](https://github.com/nst/JSONTestSuite)** 绝大部分标准,支持无限嵌套(受限于栈空间),支持注释与尾随逗号(可配置) ### 2、设计 -**RyanJson设计时大量借鉴了 [json](https://api.gitee.com/Lamdonn/json) 和 [cJSON](https://github.com/DaveGamble/cJSON) ! ** +**RyanJson设计时借鉴了 [json](https://api.gitee.com/Lamdonn/json) 和 [cJSON](https://github.com/DaveGamble/cJSON) !** Json语法是**JavaScript**对象语法的子集,可通过下面两个连接学习json语法。 @@ -36,7 +34,7 @@ Json语法是**JavaScript**对象语法的子集,可通过下面两个连接 [Parsing JSON is a Minefield 建议看看](https://seriot.ch/projects/parsing_json.html) -在RyanJson中,**结构体表示最小存储单元(键值对)**,通过单链表组织,结构如下: +RyanJson 的核心在于对内存布局的精细控制,**结构体表示最小存储单元(键值对)**,通过单链表组织数据,结构如下: ```c // Json 的最基础节点,所有 Json 元素都由该节点表示。 @@ -46,13 +44,20 @@ struct RyanJsonNode { struct RyanJsonNode *next; // 单链表节点指针 - /* - * 在 next 后紧跟一个字节的 flag,用于描述节点的核心信息: + /** + * @brief RyanJson 节点结构体 + * 每个节点由链表连接,包含元数据标识 (Flag) 与动态载荷存储区。 * - * 位分布如下: + * 内存布局: + * [ next指针 | flag(1字节) | padding/指针空间 | 动态载荷区 ] + * + * @brief 节点元数据标识 (Flag) + * 紧跟 next 指针后,利用 1 字节位域描述节点类型及存储状态。 + * + * flag 位分布定义: * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 * ----------------------------------------------------- - * 保留 KeyLen KeyLen HasKey NumExt Type2 Type1 Type0 + * strMode KeyLen KeyLen HasKey NumExt Type2 Type1 Type0 * * 各位含义: * - bit0-2 : 节点类型 @@ -61,192 +66,233 @@ struct RyanJsonNode * * - bit3 : 扩展位 * Bool 类型:0=false, 1=true - * Number 类型:0=int, 1=double + * Number 类型:0=int(4字节), 1=double(8字节) * * - bit4 : 是否包含 Key * 0=无 Key(数组元素) * 1=有 Key(对象成员) * * - bit5-6 : Key 长度字段字节数 - * 00=1字节 (≤255) - * 01=2字节 (≤65535) - * 10=3字节 (≤16M) + * 00=1字节 (≤UINT8_MAX) + * 01=2字节 (≤UINT16_MAX) + * 10=3字节 (≤UINT24_MAX) * 11=4字节 (≤UINT32_MAX) * - * - bit7 : 保留位(未来可用于压缩标记、特殊类型等) - */ - - /* - * flag 后若节点包含 key / strValue,则跟随一个指针, - * 指向存储区:[ keyLen | key | stringValue ] - * 其中 keyLen 的大小由 flag 中的长度信息决定(最多 4 字节)。 - * - * 在指针之后,根据节点类型存储具体数据: - * - null / bool : 由 flag 表示 - * - string : 由上述指针指向 - * - number : 根据 flag 决定存储 int(4字节) 或 double(8字节) - * - object : 动态分配空间存储子节点,链表结构如下: - * - * { - * "name": "RyanJson", - * next ( - * "version": "xxx", - * next ( - * "repository": "https://github.com/Ryan-CW-Code/RyanJson", - * next ( - * "keywords": ["json", "streamlined", "parser"], - * next ( - * "others": { ... } - * ))) - * } - */ - - /* - * 设计特点: - * - 一个 Json 节点最多 malloc 两次(一次节点本身,一次可选的 key/stringValue), - * 对嵌入式系统非常友好,减少 malloc 头部开销, 尽可能的减少内存碎片。 + * - bit7 : 表示key / strValue 存储模式 + * 0:inline 模式, 1=ptr 模式 * - * - key 和 stringValue 必须通过指针管理: - * * 如果直接放在节点里,虽然只需一次 malloc, - * 但修改场景会遇到替换/释放困难。 - * * 用户可能传递的 Json 对象不是指针,无法直接替换节点, - * 要求应用层传递指针会增加侵入性,不符合“应用层无需修改”的目标。 + * @brief 动态载荷存储区 + * 目的: + * - 在保持 API 易用性和稳定性的同时,最大限度减少 malloc 调用次数。 + * - 尤其在嵌入式平台,malloc 代价高昂:不仅有堆头部空间浪费,还会产生内存碎片。 + * - 通过利用结构体内的对齐填充 (Padding) 和指针空间,形成一个灵活的缓冲区。 + * + * 存储策略: + * 利用结构体内存对齐产生的 Padding(如 Flag 后的空隙)以及原本用于存储指针的空间,形成一个缓冲区 + * 若节点包含 key / strValue,则可能有两种方案: + * 1. inline 模式 (小数据优化) + * - 当 (KeyLen + Key + Value) 的总长度 ≤ 阈值时,直接存储在结构体内部。 + * - 阈值计算公式: + * 阈值 = Padding + sizeof(void*) + (malloc头部空间的一半),再向上对齐到字节边界。 + * 举例: + * - 内存对齐:4字节 + * - malloc头部空间:8字节 + * - 可用空间 = 3 (flag后padding) + 4 (指针空间) + 4 (malloc头部一半) + * - 向上对齐后得到阈值12字节 + * - 存储布局: + * [ KeyLen | Key | Value ] + * 起始地址即为 flag 之后,数据紧凑排列,无需额外 malloc。 + * + * 2. ptr 模式 (大数据) + * - 当数据长度 > 阈值时,结构体存储一个指针,指向独立的堆区。 + * - 存储布局: + * [ KeyLen | *ptr ] -> (ptr指向) [ Key | Value ] + * - KeyLen 的大小由 flag 中的长度字段决定 (最多 4 字节)。 + * - 这样保证大数据不会撑爆结构体,同时保持 API 一致性。 + + * 其他类型的存储: + * - null / bool : 由 flag 位直接表示,无需额外空间。 + * - number : 根据 flag 扩展位决定存储 int(4字节) 或 double(8字节)。 + * - object : 动态分配空间存储子节点,采用链表结构。 + * + * 设计考量: + * - malloc 在嵌入式平台的开销: + * * RTT 最小内存管理算法中,malloc 头部约 12 字节(可以考虑tlsf算法头部空间仅4字节,内存碎片也控制的很好,适合物联网应用)。 + * * 一个 RyanJson 节点本身可能只有个位数字节,头部空间就让内存占用翻倍。 + * - 因此: + * * 小数据尽量 inline 存储,避免二次 malloc。 + * * 大数据 fallback 到 ptr 模式,保证灵活性。 + * - 修改场景: + * * 理想情况:节点结构体后面直接跟 key/strValue,修改时释放并重新申请节点。 + * * 但这样 changKey/changStrValue 接口改动太大,用户层需要修改指针,代价高。 + * * 实际策略:提供就地修改接口。 + * - 若新值长度 ≤ 原有 inline 缓冲区,直接覆盖。 + * - 若超过阈值,自动切换到 ptr 模式,用户层无需关心。 * - * - 因此采用指针方式,保证灵活性和低侵入性。 + * 链表结构示例: + * { + * "name": "RyanJson", + * next ( + * "version": "xxx", + * next ( + * "repository": "https://github.com/Ryan-CW-Code/RyanJson", + * next ( + * "keywords": [ + * "json", + * next ( + * "streamlined", + * next ( + * "parser" + * )) + * ], + * next ( + * "others": { ... } + * } */ }; typedef struct RyanJsonNode *RyanJson_t; ``` -### 3、测试体系 +### 3、测试与质量保障 -**LLVM模糊测试**(核心亮点),模糊测试是 RyanJson 的 **核心稳定性保障**。 +#### 🧪 专项基础功能测试 + +| 测试类别 | 测试目标 | +| ------------------------ | ------------------------------------------------------- | +| **修改 Json 节点测试** | 验证字段动态更新及存储模式(Inline/Ptr)的自动切换逻辑 | +| **比较 Json 节点测试** | 验证节点及其属性的递归深度一致性比较与逻辑等价性 | +| **创建 Json 节点树测试** | 验证全类型节点的构造初始化及深层嵌套结构的正确性 | +| **删除 Json 节点测试** | 验证节点及其子项的递归内存回收机制,防止内存泄漏 | +| **分离 Json 节点测试** | 验证节点的分离操作、所属权转移及引用关系的生命周期管理 | +| **复制 Json 节点测试** | 验证对象深拷贝 (Deep Copy) 的数据完整性与拓扑结构一致性 | +| **Json 循环测试** | 验证数组/对象迭代器在不同数据规模下的稳定性与边界行为 | +| **文本解析 Json 测试** | 验证复杂 JSON 文本解析的健壮性及内存映射的准确性 | +| **替换 Json 节点测试** | 验证成员节点就地替换时的内存复用策略与逻辑一致性 | + +#### 🔍**LLVM Fuzzer 模糊测试**(核心亮点) + +LLVM Fuzzing 模糊测试是 RyanJson 的 **核心稳定性保障**。 **[点击在线查看覆盖率信息](https://ryan-cw-code.github.io/RyanJson/)** -- **千万级测试样本**:[LLVM Fuzzer](https://llvm.org/docs/LibFuzzer.html) 自动生成并执行上亿级随机与非法 JSON 输入。 -- **覆盖率极高**:分支覆盖率 **99.9%**,无崩溃、无泄漏。 -- **鲁棒性验证**:内存申请失败、扩容失败、非法转义字符、尾随逗号、嵌套过深、随机类型切换。 -- **内存安全验证**:结合 Sanitizer 工具链,确保无泄漏、无悬空指针、无越界。 - -| 测试类别 | 测试目标 | -| ------------------------------------- | ------------------------ | -| 内存故障测试 | 验证内存不足时的健壮性 | -| 解析 Json 节点测试 | 随机非法/合法输入解析 | -| 循环遍历删除节点测试 | 确保链表删除安全性 | -| 循环遍历分离节点测试 | 验证节点分离逻辑正确性 | -| Json 压缩去除转义测试 | 检查转义字符处理健壮性 | -| Json 打印和解析测试 | 序列化与反序列化一致性 | -| 循环遍历获取 Value 测试 | 确保随机访问稳定性 | -| 循环遍历随机复制 Json 节点测试 | 验证深拷贝与浅拷贝安全性 | -| 循环遍历随机修改节点类型/创建节点测试 | 动态扩展与类型切换能力 | - -#### 📊 手写的专项基础测试用例 - -| 测试类别 | 测试目标 | -| -------------------- | --------------------------- | -| 文本解析 Json 测试 | 基础解析与加载能力验证 | -| 创建 Json 节点树测试 | 节点生成与结构正确性检查 | -| 修改 Json 节点测试 | 字段修改的条件覆盖与正确性 | -| 删除 Json 节点测试 | 各类删除场景与边界条件验证 | -| 分离 Json 节点测试 | 节点分离与引用关系稳定性 | -| 替换 Json 节点测试 | 节点替换行为与一致性验证 | -| 比较 Json 节点测试 | 节点比较与等价性一致性 | -| 复制 Json 节点测试 | 深拷贝/浅拷贝的语义与完整性 | -| Json 循环测试 | 遍历性能与迭代稳定性 | - -### 4、代码质量与规范 - -#### ✅ 工具链全面集成 +- **上亿级测试样本**:[LLVM Fuzzer](https://llvm.org/docs/LibFuzzer.html) 自动生成并执行上亿级随机与非法 JSON 输入 +- **覆盖率极高**:**分支覆盖率 100%**(希望以后也能保持),无崩溃、无泄漏 +- **鲁棒性验证**:内存申请失败、扩容失败、非法转义字符、尾随逗号、嵌套过深、随机类型切换 +- **内存安全验证**:结合 **[Sanitizer](https://clang.llvm.org/docs/index.html#sanitizers)** 工具链,确保无泄漏、无悬空指针、无越界 + +| 测试类别 | 测试目标 | +| -------------------------- | --------------------------------------------------------- | +| **内存故障模拟测试** | 验证在堆内存耗尽 (OOM) 场景下的异常回滚及系统稳定性 | +| **随机解析鲁棒性测试** | 验证对非法语法、畸形字符及极端输入的容错与边界防御能力 | +| **循环遍历删除安全性测试** | 验证链表迭代过程中动态删除节点的双向一致性与指针安全性 | +| **循环遍历分离安全性测试** | 验证在高频率迭代中分离节点后的拓扑重构与内存权属逻辑 | +| **转义序列极致压缩测试** | 验证复杂及异常转义字符在高效压缩过程中的解析完整性 | +| **序列化回环一致性测试** | 验证 JSON 对象经过“解析-打印-解析”链路后数据的不失真性 | +| **高频迭代值读取测试** | 验证在不同层级结构下随机访问 Value 字段的寻址效率与稳定性 | +| **随机压力复制安全测试** | 验证大规模深拷贝过程中内存池的利用效率与拓扑结构安全性 | +| **动态类型切换压力测试** | 验证节点在运行期进行类型强制转换与动态扩展时的内存安全性 | + +#### 🛡️ 工具链全面集成 | 工具 | 用途 | | ------------------------------------------------------------ | ------------------------------------------------------------ | -| **[Sanitizer](https://clang.llvm.org/docs/index.html#sanitizers)** | 运行时捕获内存与线程安全问题 | +| **[Sanitizer](https://clang.llvm.org/docs/index.html#sanitizers)** | 运行时检测,捕获内存泄漏、越界、数据竞争。杜绝泄漏、越界、悬空指针 | | **[clang-tidy](https://clang.llvm.org/extra/clang-tidy/#clang-tidy)** | 静态分析潜在缺陷(空指针、资源泄漏等) | | **[Cppcheck](https://cppcheck.sourceforge.io/)** | 深度扫描内存与资源问题 | | **[ClangFormat](https://clang.llvm.org/docs/ClangFormat.html)** | 统一代码风格 | +| AI 审查 | **[Gemini Code Assist](https://codeassist.google/)** 、**[coderabbitai](https://www.coderabbit.ai)** 、 **[Copilot](https://github.com/features/copilot)** 辅助优化逻辑,构建多层安全防线 | | **编译器警告** | `-Wall -Wextra`(默认)、`-Weffc++`/`-Weverything`(Clang 可选,CI 强化时开启) | -#### ✅ 检查重点覆盖 - -- 内存安全:杜绝泄漏、越界、悬空指针 -- 性能优化:减少冗余拷贝与低效算法 -- 可读性:命名规范、注释完整、逻辑清晰 - -> ✅ **成果**:实现接近语法级"**零缺陷**",长期维护成本大幅降低 - - +### 4、基准测试 -### 5、示例 - -*测试代码和示例代码可在本项目根目录`RyanJsonExample`文件夹查看。* +*测试代码和示例代码可在本项目根目录 `test` 和 `RyanJsonExample` 文件夹查看。* #### 性能测试 -**请移步文末的 RyanDocs 文档中心查看,是基于 [yyjson_benchmark](https://github.com/ibireme/yyjson_benchmark) 的测试结果** +**请移步文末的 RyanDocs 文档中心查看,是基于 [yyjson_benchmark](https://github.com/ibireme/yyjson_benchmark) 的测试结果**。(已经过时,仅供参考) #### 内存占用测试 -*(20251222 linux平台,**不考虑malloc头部空间)**测试代码可在本项目根目录`RyanJsonExample`文件夹查看。新版本内存占用更低!* +(20251222 **malloc头部空间4字节,内存对齐4字节**)测试代码可在本项目根目录`RyanJsonExample`文件夹查看。 ``` ***************************************************************************** *************************** RyanJson / cJSON / yyjson 内存对比程序 ************************** ***************************************************************************** - ---------------------------- 混合类型json数据测试 -------------------------- -json原始文本长度为 2265, 序列化后RyanJson内存占用: 3613, cJSON内存占用: 9160, yyjson内存占用: 8692 -比cJSON节省: 60.56% 内存占用, 比yyjson节省: 58.43% 内存占用 - ---------------------------- 对象占多json数据测试 -------------------------- -json原始文本长度为 3991, 序列化后RyanJson内存占用: 5436, cJSON内存占用: 11633, yyjson内存占用: 12640 -比cJSON节省: 53.27% 内存占用, 比yyjson节省: 56.99% 内存占用 - ---------------------------- 数组占多json数据测试 -------------------------- -json原始文本长度为 1205, 序列化后RyanJson内存占用: 2365, cJSON内存占用: 7340, yyjson内存占用: 5028 -比cJSON节省: 67.78% 内存占用, 比yyjson节省: 52.96% 内存占用 - ---------------------------- 小对象json 混合类型内存占用测试 -------------------------- -json原始文本长度为 90, 序列化后RyanJson内存占用: 131, cJSON内存占用: 309, yyjson内存占用: 636 -比cJSON节省: 57.61% 内存占用, 比yyjson节省: 79.40% 内存占用 - ---------------------------- 小对象json 纯字符串内存占用测试 -------------------------- -json原始文本长度为 100, 序列化后RyanJson内存占用: 144, cJSON内存占用: 339, yyjson内存占用: 636 -比cJSON节省: 57.52% 内存占用, 比yyjson节省: 77.36% 内存占用 +┌── [TEST 1 | test/RyanJsonMemoryFootprintTest.c:294] 开始执行: testMixedJsonMemory() +json原始文本长度为 2265, 序列化后RyanJson内存占用: 4912, cJSON内存占用: 11336, yyjson内存占用: 8784 +比cJSON节省: 56.67% 内存占用, 比yyjson节省: 44.08% 内存占用 +└── [TEST 1 | test/RyanJsonMemoryFootprintTest.c:294] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 2 | test/RyanJsonMemoryFootprintTest.c:295] 开始执行: testObjectJsonMemory() +json原始文本长度为 3991, 序列化后RyanJson内存占用: 7944, cJSON内存占用: 16020, yyjson内存占用: 12884 +比cJSON节省: 50.41% 内存占用, 比yyjson节省: 38.34% 内存占用 +└── [TEST 2 | test/RyanJsonMemoryFootprintTest.c:295] 结束执行: 结果 ✅ | 耗时: 1 ms + +┌── [TEST 3 | test/RyanJsonMemoryFootprintTest.c:296] 开始执行: testArrayJsonMemory() +json原始文本长度为 1205, 序列化后RyanJson内存占用: 3696, cJSON内存占用: 8680, yyjson内存占用: 5068 +比cJSON节省: 57.42% 内存占用, 比yyjson节省: 27.07% 内存占用 +└── [TEST 3 | test/RyanJsonMemoryFootprintTest.c:296] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 4 | test/RyanJsonMemoryFootprintTest.c:297] 开始执行: testSmallMixedJsonMemory() +json原始文本长度为 90, 序列化后RyanJson内存占用: 168, cJSON内存占用: 392, yyjson内存占用: 648 +比cJSON节省: 57.14% 内存占用, 比yyjson节省: 74.07% 内存占用 +└── [TEST 4 | test/RyanJsonMemoryFootprintTest.c:297] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 5 | test/RyanJsonMemoryFootprintTest.c:298] 开始执行: testSmallStringJsonMemory() +json原始文本长度为 100, 序列化后RyanJson内存占用: 216, cJSON内存占用: 472, yyjson内存占用: 648 +比cJSON节省: 54.24% 内存占用, 比yyjson节省: 66.67% 内存占用 +└── [TEST 5 | test/RyanJsonMemoryFootprintTest.c:298] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 6 | test/RyanJsonMemoryFootprintTest.c:299] 开始执行: testCompressedBusinessJsonMemory() +json原始文本长度为 551, 序列化后RyanJson内存占用: 1184, cJSON内存占用: 3788, yyjson内存占用: 3020 +比cJSON节省: 68.74% 内存占用, 比yyjson节省: 60.79% 内存占用 +└── [TEST 6 | test/RyanJsonMemoryFootprintTest.c:299] 结束执行: 结果 ✅ | 耗时: 0 ms ``` -RT-Thread平台考虑malloc**头部空间12字节**情况下,嵌入式平台下占用最高的反而是malloc的内存头开销,所以建议用户优先选择malloc头部空间小的heap管理算法 +RT-Thread平台使用最小内存算法默认 **malloc头部空间12字节,内存对齐8字节**测试代码可在本项目根目录`RyanJsonExample`文件夹查看 ``` ***************************************************************************** *************************** RyanJson / cJSON / yyjson 内存对比程序 ************************** ***************************************************************************** - ---------------------------- 混合类型json数据测试 -------------------------- -json原始文本长度为 2265, 序列化后RyanJson内存占用: 7993, cJSON内存占用: 13732, yyjson内存占用: 8752 -比cJSON节省: 41.79% 内存占用, 比yyjson节省: 8.67% 内存占用 - ---------------------------- 对象占多json数据测试 -------------------------- -json原始文本长度为 3991, 序列化后RyanJson内存占用: 10668, cJSON内存占用: 19109, yyjson内存占用: 12712 -比cJSON节省: 44.17% 内存占用, 比yyjson节省: 16.08% 内存占用 - ---------------------------- 数组占多json数据测试 -------------------------- -json原始文本长度为 1205, 序列化后RyanJson内存占用: 5449, cJSON内存占用: 10424, yyjson内存占用: 5076 -比cJSON节省: 47.73% 内存占用, 比yyjson节省: -7.35% 内存占用 - ---------------------------- 小对象json 混合类型内存占用测试 -------------------------- -json原始文本长度为 90, 序列化后RyanJson内存占用: 287, cJSON内存占用: 477, yyjson内存占用: 672 -比cJSON节省: 39.83% 内存占用, 比yyjson节省: 57.29% 内存占用 - ---------------------------- 小对象json 纯字符串内存占用测试 -------------------------- -json原始文本长度为 100, 序列化后RyanJson内存占用: 300, cJSON内存占用: 567, yyjson内存占用: 672 -比cJSON节省: 47.09% 内存占用, 比yyjson节省: 55.36% 内存占用 +┌── [TEST 1 | test/RyanJsonMemoryFootprintTest.c:294] 开始执行: testMixedJsonMemory() +json原始文本长度为 2265, 序列化后RyanJson内存占用: 7292, cJSON内存占用: 14948, yyjson内存占用: 8852 +比cJSON节省: 51.22% 内存占用, 比yyjson节省: 17.62% 内存占用 +└── [TEST 1 | test/RyanJsonMemoryFootprintTest.c:294] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 2 | test/RyanJsonMemoryFootprintTest.c:295] 开始执行: testObjectJsonMemory() +json原始文本长度为 3991, 序列化后RyanJson内存占用: 11308, cJSON内存占用: 21068, yyjson内存占用: 13016 +比cJSON节省: 46.33% 内存占用, 比yyjson节省: 13.12% 内存占用 +└── [TEST 2 | test/RyanJsonMemoryFootprintTest.c:295] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 3 | test/RyanJsonMemoryFootprintTest.c:296] 开始执行: testArrayJsonMemory() +json原始文本长度为 1205, 序列化后RyanJson内存占用: 5644, cJSON内存占用: 11284, yyjson内存占用: 5104 +比cJSON节省: 49.98% 内存占用, 比yyjson节省: -10.58% 内存占用 +└── [TEST 3 | test/RyanJsonMemoryFootprintTest.c:296] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 4 | test/RyanJsonMemoryFootprintTest.c:297] 开始执行: testSmallMixedJsonMemory() +json原始文本长度为 90, 序列化后RyanJson内存占用: 252, cJSON内存占用: 520, yyjson内存占用: 676 +比cJSON节省: 51.54% 内存占用, 比yyjson节省: 62.72% 内存占用 +└── [TEST 4 | test/RyanJsonMemoryFootprintTest.c:297] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 5 | test/RyanJsonMemoryFootprintTest.c:298] 开始执行: testSmallStringJsonMemory() +json原始文本长度为 100, 序列化后RyanJson内存占用: 272, cJSON内存占用: 620, yyjson内存占用: 676 +比cJSON节省: 56.13% 内存占用, 比yyjson节省: 59.76% 内存占用 +└── [TEST 5 | test/RyanJsonMemoryFootprintTest.c:298] 结束执行: 结果 ✅ | 耗时: 0 ms + +┌── [TEST 6 | test/RyanJsonMemoryFootprintTest.c:299] 开始执行: testCompressedBusinessJsonMemory() +json原始文本长度为 551, 序列化后RyanJson内存占用: 2032, cJSON内存占用: 4872, yyjson内存占用: 3056 +比cJSON节省: 58.29% 内存占用, 比yyjson节省: 33.51% 内存占用 +└── [TEST 6 | test/RyanJsonMemoryFootprintTest.c:299] 结束执行: 结果 ✅ | 耗时: 0 ms ``` -RFC 8259 标准测试,大部分嵌入式场景不会出现极为特殊的Unicode字符集 +#### [RFC 8259](https://github.com/nst/JSONTestSuite) 标准符合性测试 + +**RyanJson使用Double存储浮点数,超大数字会丢失精度** ***如果项目需要完全兼容Unicode字符集,可以考虑yyjson / json-c*** @@ -254,84 +300,73 @@ RFC 8259 标准测试,大部分嵌入式场景不会出现极为特殊的Unico ***************************************************************************** *************************** RyanJson / cJSON / yyjson RFC8259标准测试 ************************** ***************************************************************************** -开始 RFC 8259 JSON 测试 ---------------------------- RFC8259 RyanJson -------------------------- -1 数据不完全一致 -- 原始: {"min":-1.0e+28,"max":1.0e+28} -- 序列化: {"min":-9999999999999999583119736832.0,"max":9999999999999999583119736832.0} -2 数据不完全一致 -- 原始: [123e65] -- 序列化: [12300000.0] -应该失败,但是成功: 123, len: 4 -3 数据不完全一致 -- 原始: [-123123e100000] -- 序列化: [-123123.0] -4 数据不完全一致 -- 原始: {"foo\u0000bar":42} -- 序列化: {"foo":42} -5 数据不完全一致 -- 原始: [1E22] -- 序列化: [100.0] -6 数据不完全一致 -- 原始: [1eE2] -- 序列化: [100.0] -应该失败,但是成功: [1eE2], len: 6 -应该成功,但是失败: [20e1], len: 6 -7 数据不完全一致 -- 原始: [123e45] -- 序列化: [12300000.0] -8 数据不完全一致 -- 原始: ["\u0000"] -- 序列化: [""] -9 数据不完全一致 -- 原始: [123123e100000] -- 序列化: [123123.0] -应该成功,但是失败: [0e1], len: 5 -10 数据不完全一致 -- 原始: [123.456e78] -- 序列化: [12345600000.0] -11 数据不完全一致 -- 原始: [-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] -- 序列化: [-1.000000e-78] -12 数据不完全一致 -- 原始: {"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430"} -- 序列化: {"title":"Полтора Землекопа"} -RFC 8259 JSON: (318/322) - ---------------------------- RFC8259 cJSON -------------------------- -应该失败,但是成功: [1.], len: 4 -1 数据不完全一致 -- 原始: {"min":-1.0e+28,"max":1.0e+28} -- 序列化: {"min":-1e+28,"max":1e+28} -2 数据不完全一致 -- 原始: [ - ] -- 序列化: [] -应该失败,但是成功: [ - ], len: 3 -3 数据不完全一致 -- 原始: ["\uqqqq"] -- 序列化: [""] -应该失败,但是成功: ["\uqqqq"], len: 10 -应该失败,但是成功: [2.e-3], len: 7 -应该失败,但是成功: [-2.], len: 5 -应该失败,但是成功: [-.123], len: 7 -应该失败,但是成功: 123, len: 4 -4 数据不完全一致 -- 原始: [-123123e100000] -- 序列化: [null] -5 数据不完全一致 -- 原始: [-1e+9999] -- 序列化: [null] -6 数据不完全一致 -- 原始: {"foo\u0000bar":42} -- 序列化: {"foo":42} +┌── [TEST 1 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:282] 开始执行: testRFC8259RyanJson() +1 数据不完全一致 -- 原始: [-1e+9999] -- 序列化: [null] +2 数据不完全一致 -- 原始: [123123e100000] -- 序列化: [null] +3 数据不完全一致 -- 原始: [-123123e100000] -- 序列化: [null] +4 数据不完全一致 -- 原始: [0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] -- 序列化: [null] +5 数据不完全一致 -- 原始: [1.5e+9999] -- 序列化: [null] +RFC 8259 JSON: (322/322) +└── [TEST 1 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:282] 结束执行: 结果 ✅ | 耗时: 69 ms + +┌── [TEST 2 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:283] 开始执行: testRFC8259yyjson() +RFC 8259 JSON: (322/322) +└── [TEST 2 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:283] 结束执行: 结果 ✅ | 耗时: 8 ms + +┌── [TEST 3 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:284] 开始执行: testRFC8259cJSON() +应该失败,但是成功: [012], len: 5 应该失败,但是成功: [2.e+3], len: 7 +1 数据不完全一致 -- 原始: [0e1] -- 序列化: [0] +应该失败,但是成功: 123, len: 4 应该失败,但是成功: [0.e1], len: 6 -7 数据不完全一致 -- 原始: -- 序列化: [""] -8 数据不完全一致 -- 原始: [0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] -- 序列化: [null] -9 数据不完全一致 -- 原始: ["\u0000"] -- 序列化: [""] -10 数据不完全一致 -- 原始: {} -- 序列化: {} -11 数据不完全一致 -- 原始: ["a -- 序列化: ["a"] +2 数据不完全一致 -- 原始: [-1e+9999] -- 序列化: [null] +3 数据不完全一致 -- 原始: [123123e100000] -- 序列化: [null] +4 数据不完全一致 -- 原始: [ -- 序列化: [""] +应该失败,但是成功: ["new +line"], len: 12 +5 数据不完全一致 -- 原始: [-123123e100000] -- 序列化: [null] +6 数据不完全一致 -- 原始: [1E+2] -- 序列化: [100] +应该失败,但是成功: [1.], len: 4 +7 数据不完全一致 -- 原始: [0e+1] -- 序列化: [0] +8 数据不完全一致 -- 原始: ["a -- 序列化: ["a"] 应该失败,但是成功: ["a, len: 7 应该失败,但是成功: [-012], len: 6 +9 数据不完全一致 -- 原始: [0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] -- 序列化: [null] +10 数据不完全一致 -- 原始: ["\uqqqq"] -- 序列化: [""] +应该失败,但是成功: ["\uqqqq"], len: 10 +11 数据不完全一致 -- 原始: [ + ] -- 序列化: [] +应该失败,但是成功: [ + ], len: 3 12 数据不完全一致 -- 原始: [ -- 序列化: [] 应该失败,但是成功: [, len: 3 -应该失败,但是成功: [012], len: 5 -应该失败,但是成功: ["new -line"], len: 12 -13 数据不完全一致 -- 原始: [ -- 序列化: [""] +13 数据不完全一致 -- 原始: [1.5e+9999] -- 序列化: [null] +14 数据不完全一致 -- 原始: -- 序列化: [""] +应该失败,但是成功: [2.e-3], len: 7 +应该失败,但是成功: [2.e3], len: 6 应该失败,但是成功: [" "], len: 5 -14 数据不完全一致 -- 原始: [1.5e+9999] -- 序列化: [null] +15 数据不完全一致 -- 原始: [1e+2] -- 序列化: [100] 应该失败,但是成功: [-01], len: 5 -15 数据不完全一致 -- 原始: [123123e100000] -- 序列化: [null] -16 数据不完全一致 -- 原始: [-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] -- 序列化: [-1e-78] -17 数据不完全一致 -- 原始: {"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430"} -- 序列化: {"title":"Полтора Землекопа"} -应该失败,但是成功: [2.e3], len: 6 +应该失败,但是成功: [-.123], len: 7 +16 数据不完全一致 -- 原始: {} -- 序列化: {} +17 数据不完全一致 -- 原始: [20e1] -- 序列化: [200] +18 数据不完全一致 -- 原始: [123.456e-789] -- 序列化: [0] +19 数据不完全一致 -- 原始: [123e-10000000] -- 序列化: [0] +应该失败,但是成功: [-2.], len: 5 RFC 8259 JSON: (305/322) - ---------------------------- RFC8259 yyjson -------------------------- -1 数据不完全一致 -- 原始: {"min":-1.0e+28,"max":1.0e+28} -- 序列化: {"min":-1e28,"max":1e28} -2 数据不完全一致 -- 原始: [-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] -- 序列化: [-1e-78] -3 数据不完全一致 -- 原始: {"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430"} -- 序列化: {"title":"Полтора Землекопа"} -RFC 8259 JSON: (322/322) -|||----------->>> area = 0, size = 0 +└── [TEST 3 | test/RFC8259Test/RyanJsonRFC8259JsonTest.c:284] 结束执行: 结果 ✅ | 耗时: 7 ms ``` -### 6、局限性 +### 5、局限性与注意事项 -- 使用`int / double`表示 JSON number 类型,**可能存在精度丢失**。建议 64 位数值用字符串表示。 -- **对象中允许有重复的key**,RyanJson库采用**单向链表**,链表结构会返回第一个匹配项。 +- **数值精度**:内部使用 `int` / `double` 存储 Number。对于超过 double 精度的 64 位整数或高精度浮点数,double内部使用 snprintf 打印,如果你的平台不支持科学计数法,建议使用 String 类型存储以避免精度丢失。 +- **重复 Key**:RyanJson 允许对象中存在重复 Key(解析时不报错),但在查找时只会返回链表中第一个匹配项。 -### 7、文档 +### 6、文档 📂 示例代码:`RyanJsonExample` 文件夹 📖 文档中心:RyanDocs -📧 联系方式:1831931681@qq.com +📧 联系与支持:如有任何疑问或商业合作需求,请联系:`1831931681@qq.com` diff --git a/RyanJson/RyanJson.c b/RyanJson/RyanJson.c index f8689db..7827f08 100644 --- a/RyanJson/RyanJson.c +++ b/RyanJson/RyanJson.c @@ -1,2118 +1,295 @@ -#include "RyanJson.h" - -#ifdef isEnableFuzzer -#undef RyanJsonNestingLimit -#define RyanJsonNestingLimit 350 - -#undef RyanJsonSnprintf -#include -#include - -static uint32_t RyanJsonRandRange(uint32_t min, uint32_t max) -{ - // int32_t seedp = (int32_t)time(NULL); - // return min + rand_r(&seedp) % (max - min + 1); - - static uint64_t state = 0; - // 初始化一次种子 - if (state == 0) { state = (uint64_t)time(NULL); } - - // Xorshift64* 算法 - state ^= state >> 12; - state ^= state << 25; - state ^= state >> 27; - uint64_t result = state * 2685821657736338717ULL; - - return min + (uint32_t)(result % (max - min + 1)); -} - -static int32_t RyanJsonSnprintf(char *buf, size_t size, const char *fmt, ...) -{ - static uint32_t jsonsnprintCount = 1; - jsonsnprintCount++; - if (jsonsnprintCount % RyanJsonRandRange(10, 500) == 0) { return 0; }; - - va_list args; - va_start(args, fmt); // 每 500 次随机触发一次“失败” - - int32_t ret = vsnprintf(buf, size, fmt, args); - - va_end(args); - return ret; -} -#endif - -typedef struct -{ - uint32_t remainSize; // 待解析字符串剩余长度 - uint32_t depth; // How deeply nested (in arrays/objects) is the input at the current offset. - const uint8_t *currentPtr; // 待解析字符串地址 -} RyanJsonParseBuffer; - -typedef struct -{ - RyanJsonBool_e isNoAlloc; // 是否动态申请内存 - uint32_t cursor; // 解析到那个buf位置上了 - uint32_t size; // 待解析字符串剩余长度, 不动态申请内存时,到达此size大小将返回失败 - uint8_t *bufAddress; // 反序列化后的字符串地址 -} RyanJsonPrintBuffer; - -// !这部分跟 struct RyanJsonNode 要保持一致 -typedef struct -{ - RyanjsonType_e type; - RyanJsonBool_e boolIsTrueFlag; - RyanJsonBool_e numberIsDoubleFlag; - - const char *key; - const char *strValue; -} RyanJsonNodeInfo_t; - -#define _checkType(info, type) ((info) == (type)) - -/** - * @brief printBuf相关宏 - * - */ -#define printBufPutChar(printfBuf, char) \ - do { ((printfBuf)->bufAddress[(printfBuf)->cursor++] = (char)); } while (0) -#define printBufPutString(printfBuf, string, len) \ - do \ - { \ - for (uint32_t i = 0; i < (uint32_t)(len); i++) printBufPutChar(printfBuf, (string)[i]); \ - } while (0) -#define printBufCurrentPtr(printfBuf) (&((printfBuf)->bufAddress[(printfBuf)->cursor])) - -/** - * @brief parseBuf相关宏 - * - */ -#define parseBufAdvanceCurrentPrt(parseBuf, bytesToAdvance) \ - do \ - { \ - (parseBuf)->currentPtr += (bytesToAdvance); \ - (parseBuf)->remainSize -= (bytesToAdvance); \ - } while (0) - -// 是否还有可读的待解析文本在指定索引处 -#define parseBufHasRemainAtIndex(parseBuf, index) ((index) < (parseBuf)->remainSize) -// 是否还有可读的待解析文本 -#define parseBufHasRemainBytes(parseBuf, bytes) ((parseBuf)->remainSize >= (bytes)) -#define parseBufHasRemain(parseBuf) parseBufHasRemainBytes(parseBuf, 1) - -/** - * @brief 尝试向前移动解析缓冲区指针 - * - * @param parseBuf - * @param bytesToAdvance - * @return RyanJsonBool_e - */ -static RyanJsonBool_e parseBufTyrAdvanceCurrentPrt(RyanJsonParseBuffer *parseBuf, uint32_t bytesToAdvance) -{ - RyanJsonCheckAssert(NULL != parseBuf); - -#ifdef isEnableFuzzer - static uint32_t count = 0; - count++; - if (0 == count % RyanJsonRandRange(10, 2000)) { return RyanJsonFalse; } -#endif - - if (parseBufHasRemainBytes(parseBuf, bytesToAdvance)) - { - parseBufAdvanceCurrentPrt(parseBuf, bytesToAdvance); - return RyanJsonTrue; - } - - return RyanJsonFalse; -} - -/** - * @brief 跳过无意义的字符 - * - * @param parseBuf - * @return RyanJsonBool_e - */ -static RyanJsonBool_e parseBufSkipWhitespace(RyanJsonParseBuffer *parseBuf) -{ - RyanJsonCheckAssert(NULL != parseBuf); - -#ifdef isEnableFuzzer - static uint32_t count = 0; - count++; - if (0 == count % RyanJsonRandRange(10, 2000)) { return RyanJsonFalse; } -#endif - - const uint8_t *cursor = parseBuf->currentPtr; - while (parseBufHasRemain(parseBuf) && *cursor && (' ' == *cursor || '\n' == *cursor || '\r' == *cursor)) - { - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - // 更新本地指针以反映 buf->address 的变化(若 parseBufTyrAdvanceCurrentPrt 移动 address) - cursor = parseBuf->currentPtr; - } - - return RyanJsonTrue; -} - -static RyanJsonMalloc_t jsonMalloc = NULL; -static RyanJsonFree_t jsonFree = NULL; -static RyanJsonRealloc_t jsonRealloc = NULL; - -static RyanJsonBool_e RyanJsonParseValue(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out); -static RyanJsonBool_e RyanJsonPrintValue(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf, uint32_t depth, RyanJsonBool_e format); - -static RyanJson_t RyanJsonCreateObjectAndKey(const char *key); -static RyanJson_t RyanJsonCreateArrayAndKey(const char *key); - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-align" -static uint8_t *RyanJsonGetHiddePrt(RyanJson_t pJson) -{ - RyanJsonCheckAssert(NULL != pJson); - return *(uint8_t **)(RyanJsonGetPayloadPtr(pJson) + RyanJsonAlign); -} -static void RyanJsonSetHiddePrt(RyanJson_t pJson, uint8_t *hiddePrt) -{ - RyanJsonCheckAssert(NULL != pJson); - RyanJsonCheckAssert(NULL != hiddePrt); - *(uint8_t **)(RyanJsonGetPayloadPtr(pJson) + RyanJsonAlign) = hiddePrt; -} - -/** - * @brief 获取隐藏指针在某个索引处的值 - * - * @param pJson - * @param index - * @return uint8_t* - */ -static uint8_t *RyanJsonGetHiddenPtrAt(RyanJson_t pJson, uint32_t index) -{ - RyanJsonCheckAssert(NULL != pJson); - return (uint8_t *)(RyanJsonGetHiddePrt(pJson) + (index)); -} -#pragma GCC diagnostic pop - -union RyanJsonUint32Bytes { - uint32_t value; - uint8_t buf[4]; -}; -static void RyanJsonSetLenKey(RyanJson_t pJson, uint32_t value) -{ - RyanJsonCheckAssert(NULL != pJson); - uint8_t *buf = RyanJsonGetHiddenPtrAt(pJson, 0); - uint8_t len = RyanJsonGetPayloadEncodeKeyLenByFlag(pJson); - RyanJsonCheckAssert(len <= 4); - - union RyanJsonUint32Bytes tmpLenField = {.value = value}; - for (uint8_t i = 0; i < len; i++) { buf[i] = tmpLenField.buf[i]; } -} - -static uint32_t RyanJsonGetLenKey(RyanJson_t pJson) -{ - RyanJsonCheckAssert(NULL != pJson); - uint8_t *buf = RyanJsonGetHiddenPtrAt(pJson, 0); - uint8_t len = RyanJsonGetPayloadEncodeKeyLenByFlag(pJson); - RyanJsonCheckAssert(len <= 4); - - union RyanJsonUint32Bytes tmpLenField = {0}; - for (uint8_t i = 0; i < len; i++) { tmpLenField.buf[i] = buf[i]; } - return tmpLenField.value; -} - -/** - * @brief 用户不要使用,仅考虑realloc增大情况,没有考虑减少 - * - * @param block - * @param oldSize - * @param newSize - * @return void* - */ -static void *RyanJsonExpandRealloc(void *block, uint32_t oldSize, uint32_t newSize) -{ - RyanJsonCheckAssert(NULL != block); - if (NULL != jsonRealloc) { return jsonRealloc(block, newSize); } - - void *newBlock = jsonMalloc(newSize); - RyanJsonCheckReturnNull(NULL != newBlock); - - if (NULL != block) - { - RyanJsonMemcpy(newBlock, block, oldSize); - jsonFree(block); - } - return newBlock; -} - -/** - * @brief 计算长度字段所需的字节数 - * - * @param len - * @return uint8_t - */ -static uint8_t RyanJsonCalcLenBytes(uint32_t len) -{ - if (len < 0xff) { return 0; } - if (len < 0xffff) { return 1; } - if (len < 0xffffff) { return 2; } - return 3; -} - -/** - * @brief 提供内存钩子函数 - * - * @param userMalloc - * @param userFree - * @param userRealloc 可以为NULL - * @return RyanJsonBool_e - */ -RyanJsonBool_e RyanJsonInitHooks(RyanJsonMalloc_t userMalloc, RyanJsonFree_t userFree, RyanJsonRealloc_t userRealloc) -{ - RyanJsonCheckReturnFalse(NULL != userMalloc && NULL != userFree); - - jsonMalloc = userMalloc; - jsonFree = userFree; - jsonRealloc = userRealloc; - return RyanJsonTrue; -} - -/** - * @brief 安全的浮点数比较 - * - * @param a - * @param b - * @return RyanJsonBool_e - */ -static RyanJsonBool_e compare_double(double a, double b) -{ - double diff = fabs(a - b); - double absA = fabs(a); - double absB = fabs(b); - double maxVal = (absA > absB ? absA : absB); - - // 允许的容差:相对误差 + 绝对误差 - double epsilon = DBL_EPSILON * maxVal; - double minTolerance = 1e-12; // 可调的绝对容差 - - return diff <= (epsilon > minTolerance ? epsilon : minTolerance); -} - -/** - * @brief 申请buf容量, 决定是否进行扩容 - * - * @param buf - * @param needed - * @return RyanJsonBool_e - */ -static RyanJsonBool_e printBufAppend(RyanJsonPrintBuffer *printfBuf, uint32_t needed) -{ - RyanJsonCheckAssert(NULL != printfBuf && NULL != printfBuf->bufAddress); - - needed += printfBuf->cursor; - - // 当前 buf 中有足够的空间 - if (needed < printfBuf->size) { return RyanJsonTrue; } - - // 不使用动态内存分配 - RyanJsonCheckReturnFalse(RyanJsonFalse == printfBuf->isNoAlloc); - - uint32_t size = needed + RyanJsonPrintfPreAlloSize; - char *address = (char *)RyanJsonExpandRealloc(printfBuf->bufAddress, printfBuf->size, size); - RyanJsonCheckReturnFalse(NULL != address); - - printfBuf->size = size; - printfBuf->bufAddress = (uint8_t *)address; - return RyanJsonTrue; -} - -void *RyanJsonGetValue(RyanJson_t pJson) -{ - RyanJsonCheckReturnNull(NULL != pJson); - - uint32_t len = RyanJsonAlign; - if (RyanJsonIsKey(pJson) || RyanJsonIsString(pJson)) - { - len += sizeof(void *); - // jsonLog(" keyLen: %d, keyLenField: %d, \r\n", RyanJsonGetLenKey(pJson), RyanJsonGetPayloadEncodeKeyLenByFlag(pJson)); - } - - return RyanJsonGetPayloadPtr(pJson) + len; -} - -char *RyanJsonGetKey(RyanJson_t pJson) -{ - RyanJsonCheckReturnNull(NULL != pJson); - return (char *)RyanJsonGetHiddenPtrAt(pJson, RyanJsonGetPayloadEncodeKeyLenByFlag(pJson)); -} - -char *RyanJsonGetStringValue(RyanJson_t pJson) -{ - RyanJsonCheckReturnNull(NULL != pJson); - - uint32_t len = 0; - if (RyanJsonIsKey(pJson)) { len = RyanJsonGetPayloadEncodeKeyLenByFlag(pJson) + RyanJsonGetLenKey(pJson) + 1U; } - - return (char *)RyanJsonGetHiddenPtrAt(pJson, len); -} - -static RyanJsonBool_e RyanJsonChangeString(RyanJson_t pJson, RyanJsonBool_e isNew, const char *key, const char *strValue) -{ - RyanJsonCheckAssert(NULL != pJson); - - uint32_t keyLen = 0; // key的长度 - uint8_t keyLenField = 0; // 记录key长度需要几个字节 - uint32_t strValueLen = 0; // stringValue的长度 - - uint32_t mallocSize = 0; - - // 获取需要 malloc 的设备 - if (NULL != key) - { - keyLen = RyanJsonStrlen(key); - keyLenField = RyanJsonCalcLenBytes(keyLen); - mallocSize += keyLen + keyLenField + 1 + 1; - -#ifdef isEnableFuzzer - { - RyanJsonAssert(0 == RyanJsonCalcLenBytes(0xff - 1)); - RyanJsonAssert(1 == RyanJsonCalcLenBytes(0xffff - 1)); - RyanJsonAssert(2 == RyanJsonCalcLenBytes(0xffffff - 1)); - RyanJsonAssert(3 == RyanJsonCalcLenBytes(UINT32_MAX - 1)); - } -#endif - } - - if (NULL != strValue) - { - strValueLen = RyanJsonStrlen(strValue); - mallocSize += strValueLen + 1; - } - if (0 == mallocSize) { return RyanJsonTrue; } - - // 申请新的空间 - uint8_t *newPtr = (uint8_t *)jsonMalloc(mallocSize); - if (NULL == newPtr) { return RyanJsonFalse; } - - // 释放旧的内存 - uint8_t *oldPrt = NULL; - if (RyanJsonFalse == isNew) - { - RyanJsonCheckAssert(RyanJsonIsKey(pJson) || RyanJsonIsString(pJson)); - oldPrt = RyanJsonGetHiddePrt(pJson); - } - RyanJsonSetHiddePrt(pJson, newPtr); - - // 设置key - if (NULL != key) - { - RyanJsonSetPayloadWhiteKeyByFlag(pJson, RyanJsonTrue); - RyanJsonSetPayloadEncodeKeyLenByFlag(pJson, keyLenField); - RyanJsonSetLenKey(pJson, keyLen); - - jsonLog(" keyLen: %d, keyLenField: %d, \r\n", RyanJsonGetLenKey(pJson), RyanJsonGetPayloadEncodeKeyLenByFlag(pJson)); - if (0 != keyLen) { RyanJsonMemcpy(RyanJsonGetKey(pJson), key, keyLen); } - RyanJsonGetKey(pJson)[keyLen] = '\0'; - } - else - { - RyanJsonSetPayloadWhiteKeyByFlag(pJson, RyanJsonFalse); - RyanJsonSetPayloadEncodeKeyLenByFlag(pJson, 0); - } - - // 设置字符串值 - if (NULL != strValue) - { - jsonLog("stLen: %d, strValue: %s \r\n", strValueLen, strValue); - if (0 != strValueLen) { RyanJsonMemcpy(RyanJsonGetStringValue(pJson), strValue, strValueLen); } - RyanJsonGetStringValue(pJson)[strValueLen] = '\0'; - } - - if (oldPrt) { jsonFree(oldPrt); } - return RyanJsonTrue; -} - -static RyanJson_t RyanJsonNewNode(RyanJsonNodeInfo_t *info) -{ - RyanJsonCheckAssert(NULL != info); - - // 加1是flag的空间 - uint32_t size = sizeof(struct RyanJsonNode) + RyanJsonAlign; - - if (_checkType(info->type, RyanJsonTypeNumber)) - { - if (RyanJsonFalse == info->numberIsDoubleFlag) { size += sizeof(int32_t); } - else - { - size += sizeof(double); - } - } - else if (_checkType(info->type, RyanJsonTypeArray) || _checkType(info->type, RyanJsonTypeObject)) { size += sizeof(RyanJson_t); } - - if (NULL != info->key || _checkType(info->type, RyanJsonTypeString)) { size += sizeof(void *); } - - RyanJson_t pJson = (RyanJson_t)jsonMalloc((size_t)size); - if (NULL != pJson) - { - // 只清空结构体就行了 - RyanJsonMemset(pJson, 0, size); // 这个size很小,没有优化的必要,直接memset吧 - - RyanJsonSetType(pJson, info->type); - - RyanJsonCheckCode(RyanJsonTrue == RyanJsonChangeString(pJson, RyanJsonTrue, info->key, info->strValue), { - jsonFree(pJson); - return NULL; - }); - - if (_checkType(info->type, RyanJsonTypeBool)) { RyanJsonSetPayloadBoolValueByFlag(pJson, info->boolIsTrueFlag); } - else if (_checkType(info->type, RyanJsonTypeNumber)) - { - RyanJsonSetPayloadNumberIsDoubleByFlag(pJson, info->numberIsDoubleFlag); - } - } - - return pJson; -} - -/** - * @brief 删除json及其子项 - * - * @param pJson - */ -void RyanJsonDelete(RyanJson_t pJson) -{ - RyanJson_t next; - - while (pJson) - { - next = pJson->next; - - // 递归删除 - if (_checkType(RyanJsonGetType(pJson), RyanJsonTypeArray) || _checkType(RyanJsonGetType(pJson), RyanJsonTypeObject)) - { - RyanJsonDelete(RyanJsonGetObjectValue(pJson)); // 递归删除子对象 - } - - if (RyanJsonIsKey(pJson) || RyanJsonIsString(pJson)) { jsonFree(RyanJsonGetHiddePrt(pJson)); } - - jsonFree(pJson); - - pJson = next; - } -} - -/** - * @brief 释放RyanJson申请的资源 - * - * @param block - */ -void RyanJsonFree(void *block) -{ - if (block) { jsonFree(block); } -} - -/** - * @brief 从字符串中获取十六进制值 - * - * @param text - * @return uint32_t 16进制值 - */ -static RyanJsonBool_e RyanJsonParseHex(const uint8_t *text, uint32_t *value) -{ - RyanJsonCheckAssert(NULL != text && NULL != value); - uint32_t valueTemp = 0; - - for (uint8_t i = 0; i < 4; ++i) - { - switch (text[i]) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': valueTemp = (valueTemp << 4) + (text[i] - '0'); break; - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': valueTemp = (valueTemp << 4) + 10 + (text[i] - 'a'); break; - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': valueTemp = (valueTemp << 4) + 10 + (text[i] - 'A'); break; - default: return RyanJsonFalse; - } - } - - *value = valueTemp; - - return RyanJsonTrue; -} - -/** - * @brief 解析文本中的数字,添加到json节点中 - * - * @param buf 解析缓冲区 - * @param key 对应的key - * @param out 用于接收解析后的pJson对象的地址 - * @return RyanJsonBool_e 成功或失败 - */ -static RyanJsonBool_e RyanJsonParseNumber(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out) -{ - RyanJsonCheckAssert(NULL != parseBuf && NULL != out); - - double number = 0.0; - int32_t sign = 1; - int32_t scale = 0; - int32_t e_sign = 1; - int32_t e_scale = 0; - RyanJsonBool_e isint = RyanJsonTrue; - - // 处理符号 - if (parseBufHasRemain(parseBuf) && '-' == *parseBuf->currentPtr) - { - sign = -1; - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9'); - } - - // 跳过前导零 - while (parseBufHasRemain(parseBuf) && '0' == *parseBuf->currentPtr) - { - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - // 前导0后面不允许跟数组,比如"0123" - RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && (*parseBuf->currentPtr < '0' || *parseBuf->currentPtr > '9')); - } - - // 整数部分 - while (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9') - { - number = number * 10.0 + (*parseBuf->currentPtr - '0'); - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - } - - // 小数部分 - if (parseBufHasRemain(parseBuf) && '.' == *parseBuf->currentPtr) - { - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9'); - - while (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9') - { - number = number * 10.0 + (*parseBuf->currentPtr - '0'); - scale--; // 每读一位小数,scale减一 - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - } - isint = RyanJsonFalse; - } - - // 指数部分 - if (parseBufHasRemain(parseBuf) && ('e' == *parseBuf->currentPtr || 'E' == *parseBuf->currentPtr)) - { - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf)); - if ('+' == *parseBuf->currentPtr || '-' == *parseBuf->currentPtr) { e_sign = ('-' == *parseBuf->currentPtr) ? -1 : 1; } - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9'); - - while (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9') - { - e_scale = e_scale * 10 + (*parseBuf->currentPtr - '0'); - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - } - isint = RyanJsonFalse; - } - - // 创建 JSON 节点 - RyanJson_t newItem = NULL; - if (RyanJsonTrue == isint && number >= INT32_MIN && number <= INT32_MAX) - { - newItem = RyanJsonCreateInt(key, (int32_t)(sign * number)); - } - else - { - // 避免 pow 调用过多,直接计算指数 - double expFactor = pow(10.0, scale + e_sign * e_scale); - newItem = RyanJsonCreateDouble(key, sign * number * expFactor); - } - - RyanJsonCheckReturnFalse(NULL != newItem); - - *out = newItem; - return RyanJsonTrue; -} - -/** - * @brief 解析文本中的字符串,添加到json节点中 - * - * @param text 带有jsonString的文本 - * @param buf 接收解析后的字符串指针的地址 - * @return RyanJsonBool_e - */ -static RyanJsonBool_e RyanJsonParseStringBuffer(RyanJsonParseBuffer *parseBuf, char **buffer) -{ - RyanJsonCheckAssert(NULL != parseBuf && NULL != buffer); - - uint32_t len = 0; - *buffer = NULL; - - // 不是字符串 - RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && '\"' == *parseBuf->currentPtr); - - RyanJsonCheckReturnFalse(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1)); - - // 获取字符串长度 - for (uint32_t i = 0;; i++) - { - RyanJsonCheckReturnFalse(parseBufHasRemainAtIndex(parseBuf, i)); - - uint8_t ch = parseBuf->currentPtr[i]; - - if (ch == '\"') { break; } - - // 检查非法控制字符 (ASCII 0–31) - RyanJsonCheckReturnFalse(ch > 0x1F); - - if (ch == '\\') // 跳过转义符号 - { - RyanJsonCheckReturnFalse(parseBufHasRemainAtIndex(parseBuf, i + 1)); - i++; - } - - len++; - } - - uint8_t *outBuffer = (uint8_t *)jsonMalloc((size_t)(len + 1U)); - RyanJsonCheckReturnFalse(NULL != outBuffer); - - uint8_t *outCurrentPtr = outBuffer; - while (parseBufHasRemain(parseBuf) && '\"' != *parseBuf->currentPtr) - { - // 普通字符 - if ('\\' != *parseBuf->currentPtr) - { - *outCurrentPtr++ = *parseBuf->currentPtr; - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - continue; - } - - // 转义字符 - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - switch (*parseBuf->currentPtr) - { - - case 'b': *outCurrentPtr++ = '\b'; break; - case 'f': *outCurrentPtr++ = '\f'; break; - case 'n': *outCurrentPtr++ = '\n'; break; - case 'r': *outCurrentPtr++ = '\r'; break; - case 't': *outCurrentPtr++ = '\t'; break; - case '\"': - case '\\': - case '/': *outCurrentPtr++ = *parseBuf->currentPtr; break; - - case 'u': { - - // 获取 Unicode 字符 - uint64_t codepoint = 0; - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 4), { goto __error; }); - uint32_t first_code = 0; - RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseHex(parseBuf->currentPtr - 3, &first_code), { goto __error; }); - // 检查是否有效 - if (first_code >= 0xDC00 && first_code <= 0xDFFF) { goto __error; } - - if (first_code >= 0xD800 && first_code <= 0xDBFF) // UTF16 代理对 - { - if (!parseBufHasRemainAtIndex(parseBuf, 2)) { goto __error; } - - if (parseBuf->currentPtr[1] != '\\' || parseBuf->currentPtr[2] != 'u') - { - goto __error; // 缺少代理的后半部分 - } - - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 6), { goto __error; }); - uint32_t second_code = 0; - RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseHex(parseBuf->currentPtr - 3, &second_code), - { goto __error; }); - if (0 == first_code || second_code < 0xDC00 || second_code > 0xDFFF) - { - goto __error; // 无效的代理后半部分 - } - - codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); - } - else - { - codepoint = first_code; - } - - /* encode as UTF-8 - * takes at maximum 4 bytes to encode: - * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ - uint8_t utf8_length; - uint8_t first_byte_mark; - if (codepoint < 0x80) - { - utf8_length = 1; // normal ascii, encoding 0xxxxxxx - first_byte_mark = 0; - } - else if (codepoint < 0x800) - { - utf8_length = 2; // two bytes, encoding 110xxxxx 10xxxxxx - first_byte_mark = 0xC0; // 11000000 - } - else if (codepoint < 0x10000) - { - utf8_length = 3; // three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx - first_byte_mark = 0xE0; // 11100000 - } - else - { - utf8_length = 4; // four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx - first_byte_mark = 0xF0; // 11110000 - } - // 不太可能发生 - // else - // { - // goto __error; // 无效的 unicode 代码点 - // } - - // encode as utf8 - for (uint8_t utf8_position = (uint8_t)(utf8_length - 1); utf8_position > 0; utf8_position--) - { - outCurrentPtr[utf8_position] = (uint8_t)((codepoint | 0x80) & 0xBF); // 10xxxxxx - codepoint >>= 6; - } - - // encode first byte - if (utf8_length > 1) { outCurrentPtr[0] = (uint8_t)((codepoint | first_byte_mark) & 0xFF); } - else - { - outCurrentPtr[0] = (uint8_t)(codepoint & 0x7F); - } - outCurrentPtr += utf8_length; - break; - } - - default: - // *outCurrentPtr++ = *buf->currentPtr; - goto __error; - } - - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - } - *outCurrentPtr = '\0'; - - // todo 不等于的话是不是应该报错? - if (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr == '\"') - { - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - } - - *buffer = (char *)outBuffer; - return RyanJsonTrue; - -__error: - jsonFree(outBuffer); - *buffer = NULL; - return RyanJsonFalse; -} - -/** - * @brief 解析文本中的string节点,添加到json节点中 - * - * @param text - * @param key 对应的key - * @param out 接收解析后的pJson对象的地址 - * @return const char* - */ -static RyanJsonBool_e RyanJsonParseString(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out) -{ - RyanJsonCheckAssert(NULL != parseBuf && NULL != out); - - char *buffer; - RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseStringBuffer(parseBuf, &buffer)); - - RyanJson_t newItem = RyanJsonCreateString(key, buffer); - RyanJsonCheckCode(NULL != newItem, { - jsonFree(buffer); - return RyanJsonFalse; - }); - - jsonFree(buffer); - *out = newItem; - return RyanJsonTrue; -} - -/** - * @brief 解析文本中的数组 - * - * @param text - * @param key 对应的key - * @param out 接收解析后的pJson对象的地址 - * @return const char* - */ -static RyanJsonBool_e RyanJsonParseArray(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out) -{ - RyanJsonCheckAssert(NULL != parseBuf && NULL != out); - - RyanJson_t newItem = RyanJsonCreateArrayAndKey(key); - RyanJsonCheckReturnFalse(NULL != newItem); - - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), { goto __error; }); - - // 空数组 - RyanJsonCheckCode(parseBufHasRemain(parseBuf), { goto __error; }); - if (*parseBuf->currentPtr == ']') { goto __next; } - - RyanJson_t prev = NULL, item; - do - { - // 跳过 ',' - if (NULL != prev) - { - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - } - - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), { goto __error; }); - - if (RyanJsonFalse == RyanJsonParseValue(parseBuf, NULL, &item)) { goto __error; } - - RyanJsonCheckAssert(RyanJsonTrue == RyanJsonInsert(newItem, UINT32_MAX, item)); - - prev = item; - - } while (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr == ','); - - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), { goto __error; }); - RyanJsonCheckCode(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr == ']', { goto __error; }); - -__next: - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - *out = newItem; - - return RyanJsonTrue; - -__error: - RyanJsonDelete(newItem); - *out = NULL; - return RyanJsonFalse; -} - -/** - * @brief 解析文本中的对象 - * - * @param text - * @param key - * @param out - * @return const char* - */ -static RyanJsonBool_e RyanJsonParseObject(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out) -{ - RyanJsonCheckAssert(NULL != parseBuf && NULL != out); - char *objKey = NULL; - - RyanJson_t newItem = RyanJsonCreateObjectAndKey(key); - RyanJsonCheckReturnFalse(NULL != newItem); - - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), { goto __error; }); - - RyanJsonCheckCode(parseBufHasRemain(parseBuf), { goto __error; }); - if (*parseBuf->currentPtr == '}') { goto __next; } - RyanJson_t prev = NULL, item; - do - { - if (NULL != prev) - { - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); // 跳过 ',' - } - - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), { goto __error; }); - - if (RyanJsonFalse == RyanJsonParseStringBuffer(parseBuf, &objKey)) { goto __error; } - - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), { goto __error; }); - - // 解析指示符 ':' - RyanJsonCheckCode(parseBufHasRemain(parseBuf) && ':' == *parseBuf->currentPtr, { goto __error; }); - - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), { goto __error; }); - - if (RyanJsonFalse == RyanJsonParseValue(parseBuf, objKey, &item)) { goto __error; } - if (objKey) - { - jsonFree(objKey); - objKey = NULL; - } - - RyanJsonCheckAssert(RyanJsonTrue == RyanJsonInsert(newItem, UINT32_MAX, item)); - - prev = item; - - } while (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr == ','); - - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), { goto __error; }); - RyanJsonCheckCode(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr == '}', { - objKey = NULL; // 由上层进行删除 - goto __error; - }); - -__next: - RyanJsonCheckCode(RyanJsonTrue == parseBufTyrAdvanceCurrentPrt(parseBuf, 1), { goto __error; }); - *out = newItem; - return RyanJsonTrue; - -__error: - if (objKey) { jsonFree(objKey); } - RyanJsonDelete(newItem); - *out = NULL; - return RyanJsonFalse; -} - -/** - * @brief 解析文本 - * - * @param text - * @param key - * @param out - * @return const char* - */ -static RyanJsonBool_e RyanJsonParseValue(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out) -{ - RyanJsonCheckAssert(NULL != parseBuf && NULL != out); - - parseBuf->depth++; - RyanJsonCheckReturnFalse(parseBuf->depth < RyanJsonNestingLimit); - - RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf)); - - *out = NULL; - - if (*parseBuf->currentPtr == '\"') { return RyanJsonParseString(parseBuf, key, out); } - if (*parseBuf->currentPtr == '{') { return RyanJsonParseObject(parseBuf, key, out); } - if (*parseBuf->currentPtr == '-' || (*parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9')) - { - return RyanJsonParseNumber(parseBuf, key, out); - } - if (*parseBuf->currentPtr == '[') { return RyanJsonParseArray(parseBuf, key, out); } - - if (parseBufHasRemainBytes(parseBuf, 4) && 0 == strncmp((const char *)parseBuf->currentPtr, "null", 4)) - { - *out = RyanJsonCreateNull(key); - RyanJsonCheckReturnFalse(NULL != *out); - - parseBufAdvanceCurrentPrt(parseBuf, 4); - return RyanJsonTrue; - } - if (parseBufHasRemainBytes(parseBuf, 5) && 0 == strncmp((const char *)parseBuf->currentPtr, "false", 5)) - { - *out = RyanJsonCreateBool(key, RyanJsonFalse); - RyanJsonCheckReturnFalse(NULL != *out); - - parseBufAdvanceCurrentPrt(parseBuf, 5); - return RyanJsonTrue; - } - if (parseBufHasRemainBytes(parseBuf, 4) && 0 == strncmp((const char *)parseBuf->currentPtr, "true", 4)) - { - *out = RyanJsonCreateBool(key, RyanJsonTrue); - RyanJsonCheckReturnFalse(NULL != *out); - - parseBufAdvanceCurrentPrt(parseBuf, 4); - return RyanJsonTrue; - } - - return RyanJsonFalse; -} - -/** - * @brief pJson 文本解析器 - * - * @param text 文本地址 - * @param requireNullTerminator 输入的字符串必须以空字符 \0 结尾,并且不附带无效数据 - * @param parseEndPtr 输出解析终止的字符位置 - * @return RyanJson_t - */ -static RyanJsonBool_e RyanJsonParseCheckNullTerminator(RyanJsonParseBuffer *parseBuf, RyanJsonBool_e requireNullTerminator) -{ - RyanJsonCheckAssert(NULL != parseBuf); - - if (requireNullTerminator) - { - // 故意不检查 - RyanJsonCheckCode(RyanJsonTrue == parseBufSkipWhitespace(parseBuf), {}); - - // 后面还有数据非空字符 - RyanJsonCheckReturnFalse(!(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr)); - - // // 后面还有数据 - // RyanJsonCheckReturnFalse(!parseBufHasRemainBytes(parseBuf, 1)); - - // // 非空字符 - // RyanJsonCheckReturnFalse(!(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr)); - } - - return RyanJsonTrue; -} - -RyanJson_t RyanJsonParseOptions(const char *text, uint32_t size, RyanJsonBool_e requireNullTerminator, const char **parseEndPtr) -{ - RyanJson_t pJson; - RyanJsonCheckReturnNull(NULL != text); - - RyanJsonParseBuffer parseBuf = {.currentPtr = (const uint8_t *)text, .remainSize = size}; - RyanJsonCheckReturnNull(RyanJsonTrue == parseBufSkipWhitespace(&parseBuf)); - - RyanJsonCheckReturnNull(RyanJsonTrue == RyanJsonParseValue(&parseBuf, NULL, &pJson)); - // 检查解析后的文本后面是否有无意义的字符 - RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseCheckNullTerminator(&parseBuf, requireNullTerminator), { - RyanJsonDelete(pJson); - return NULL; - }); - - if (parseEndPtr) { *parseEndPtr = (const char *)parseBuf.currentPtr; } - - return pJson; -} - -/** - * @brief 反序列化数字 - * - * @param pJson - * @param buf - * @return RyanJsonBool_e - */ -static RyanJsonBool_e RyanJsonPrintNumber(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf) -{ - RyanJsonCheckAssert(NULL != pJson && NULL != printfBuf); - - double numberValue; - int32_t len; - - // RyanJsonNumber 类型是一个整数 - if (RyanJsonFalse == RyanJsonGetPayloadNumberIsDoubleByFlag(pJson)) - { - // RyanJsonCheckReturnFalse(printBufAppend(buf, 21)); // 64 位整数最多包含 20 个数字字符、1 符号 - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, 11)); // 32 位整数最多包含 10 个数字字符、1 符号 - - len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printfBuf->size, "%" PRId32, RyanJsonGetIntValue(pJson)); - RyanJsonCheckReturnFalse(len > 0); // snprintf 失败 - printfBuf->cursor += (uint32_t)len; - } - else // RyanJsonNumber 的类型是浮点型 - { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, 64)); // 浮点数用64可以适应大部分情况 - numberValue = RyanJsonGetDoubleValue(pJson); - - // use full transformation within bounded space - if (fabs(floor(numberValue) - numberValue) <= DBL_EPSILON && fabs(numberValue) < 1.0e60) - { - len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printfBuf->size, "%.1lf", numberValue); - RyanJsonCheckReturnFalse(len > 0); // snprintf 失败 - } - - // use exponential form conversion beyond the limited range - else if (fabs(numberValue) < 1.0e-6 || fabs(numberValue) > 1.0e9) - { - len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printfBuf->size, "%e", numberValue); - RyanJsonCheckReturnFalse(len > 0); // snprintf 失败 - } - - // default conversion - else - { - len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printfBuf->size, "%lf", numberValue); - RyanJsonCheckReturnFalse(len > 0); // snprintf 失败 - while (len > 0 && printBufCurrentPtr(printfBuf)[len - 1] == '0' && - printBufCurrentPtr(printfBuf)[len - 2] != '.') // 删除小数部分中无效的 0 - { - len--; - } - } - printfBuf->cursor += (uint32_t)len; - } - - return RyanJsonTrue; -} - -/** - * @brief 反序列化字符串 - * - * @param strValue - * @param buf - * @return RyanJsonBool_e - */ -static RyanJsonBool_e RyanJsonPrintStringBuffer(const uint8_t *strValue, RyanJsonPrintBuffer *printfBuf) -{ - RyanJsonCheckAssert(NULL != strValue && NULL != printfBuf); - // 获取长度 - const uint8_t *strCurrentPtr = strValue; - uint32_t escapeCharCount = 0; - for (strCurrentPtr = strValue; *strCurrentPtr; strCurrentPtr++) - { - switch (*strCurrentPtr) - { - case '\"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - case '/': escapeCharCount++; break; - - default: - // 每个字节都+5肯定满足printf的需求了 - if (*strCurrentPtr < 32) { escapeCharCount += 5; } - break; - } - } - - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, (uint32_t)(strCurrentPtr - strValue) + escapeCharCount + 2U)); // 最小是\" \" - printBufPutChar(printfBuf, '\"'); - - // 没有转义字符 - if (0 == escapeCharCount) - { - printBufPutString(printfBuf, strValue, (strCurrentPtr - strValue)); - printBufPutChar(printfBuf, '\"'); - return RyanJsonTrue; - } - - strCurrentPtr = strValue; - while (*strCurrentPtr) - { - if ((*strCurrentPtr) >= ' ' && *strCurrentPtr != '\"' && *strCurrentPtr != '\\') - { - printBufPutChar(printfBuf, *strCurrentPtr++); - continue; - } - - // 转义和打印 - printBufPutChar(printfBuf, '\\'); - - switch (*strCurrentPtr) - { - case '\\': printBufPutChar(printfBuf, '\\'); break; - case '\"': printBufPutChar(printfBuf, '\"'); break; - case '\b': printBufPutChar(printfBuf, 'b'); break; - case '\f': printBufPutChar(printfBuf, 'f'); break; - case '\n': printBufPutChar(printfBuf, 'n'); break; - case '\r': printBufPutChar(printfBuf, 'r'); break; - case '\t': printBufPutChar(printfBuf, 't'); break; - - default: { - // 可以不加p有效性的判断是因为,这个RyanJson生成的字符串,RyanJson可以确保p一定是有效的 - // jsonLog("hexasdf:\\u%04X\n", codepoint); - RyanJsonCheckReturnFalse( - 5 == RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printfBuf->size, "u%04X", *strCurrentPtr)); - printfBuf->cursor += 5; // utf - break; - } - } - strCurrentPtr++; - } - - printBufPutChar(printfBuf, '\"'); - // printBufPutChar(printfBuf, '\0'); - - return RyanJsonTrue; -} - -static RyanJsonBool_e RyanJsonPrintString(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf) -{ - RyanJsonCheckAssert(NULL != pJson && NULL != printfBuf); - return RyanJsonPrintStringBuffer((const uint8_t *)RyanJsonGetStringValue(pJson), printfBuf); -} - -/** - * @brief 反序列化数组 - * - * @param pJson - * @param buf - * @param depth - * @param format - * @return RyanJsonBool_e - */ -static RyanJsonBool_e RyanJsonPrintArray(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf, uint32_t depth, RyanJsonBool_e format) -{ - RyanJsonCheckAssert(NULL != pJson && NULL != printfBuf); - - uint32_t count = 0; - RyanJson_t child = RyanJsonGetObjectValue(pJson); - - if (NULL == child) - { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, 2)); - - printBufPutChar(printfBuf, '['); - printBufPutChar(printfBuf, ']'); - return RyanJsonTrue; - } - - if (format) - { - while (child) // 检查子级中是否有数组或对象 - { - if ((RyanJsonIsArray(child) || RyanJsonIsObject(child)) && RyanJsonGetObjectValue(child)) - { - count++; - break; - } - child = child->next; - } - } - - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, (format && count) ? 2 : 1)); - - printBufPutChar(printfBuf, '['); - if (format && count) { printBufPutChar(printfBuf, '\n'); } - - child = RyanJsonGetObjectValue(pJson); - while (child) - { - // 打印起始缩进 - if (format && count) - { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, depth + 1U)); - - for (uint32_t i = 0; i <= depth; i++) { printBufPutChar(printfBuf, '\t'); } - } - - RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonPrintValue(child, printfBuf, depth + 1U, format)); - - // 打印分隔符 ',' - if (child->next) - { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, format ? 2 : 1)); - - printBufPutChar(printfBuf, ','); - if (format) - { - if (count) { printBufPutChar(printfBuf, '\n'); } - else - { - printBufPutChar(printfBuf, ' '); - } - } - } - - child = child->next; - } - - // 打印结束缩进 - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, (format && count) ? depth + 2 : 1)); - - if (format && count) - { - printBufPutChar(printfBuf, '\n'); - for (uint32_t i = 0; i < depth; i++) { printBufPutChar(printfBuf, '\t'); } - } - printBufPutChar(printfBuf, ']'); - - return RyanJsonTrue; -} - -/** - * @brief 反序列化对象 - * - * @param pJson - * @param buf - * @param depth - * @param format - * @return RyanJsonBool_e - */ -static RyanJsonBool_e RyanJsonPrintObject(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf, uint32_t depth, RyanJsonBool_e format) -{ - RyanJsonCheckAssert(NULL != pJson && NULL != printfBuf); - - RyanJson_t child = RyanJsonGetObjectValue(pJson); - - if (NULL == child) - { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, 2)); - - printBufPutChar(printfBuf, '{'); - printBufPutChar(printfBuf, '}'); - return RyanJsonTrue; - } - - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, format ? 2 : 1)); - printBufPutChar(printfBuf, '{'); - if (format) { printBufPutChar(printfBuf, '\n'); } - - while (child) - { - // 打印起始缩进 - if (format) - { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, depth + 1)); - - for (uint32_t i = 0; i <= depth; i++) { printBufPutChar(printfBuf, '\t'); } - } - - RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonPrintStringBuffer((const uint8_t *)RyanJsonGetKey(child), printfBuf)); - - // 打印指示符 ':' - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, format ? 2 : 1)); - - printBufPutChar(printfBuf, ':'); - if (format) { printBufPutChar(printfBuf, '\t'); } - - RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonPrintValue(child, printfBuf, depth + 1, format)); - - // 打印分隔符 ',' - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, (child->next ? 1 : 0) + (format ? 1 : 0))); - - if (child->next) { printBufPutChar(printfBuf, ','); } - - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, 1)); - if (format) { printBufPutChar(printfBuf, '\n'); } - - child = child->next; - } - - // 打印结束缩进 - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, format ? depth + 1 : 1)); - - if (format) - { - for (uint32_t i = 0; i < depth; i++) { printBufPutChar(printfBuf, '\t'); } - } - printBufPutChar(printfBuf, '}'); - - return RyanJsonTrue; -} - -static RyanJsonBool_e RyanJsonPrintValue(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf, uint32_t depth, RyanJsonBool_e format) -{ - RyanJsonCheckAssert(NULL != pJson && NULL != printfBuf); - - switch (RyanJsonGetType(pJson)) - { - case RyanJsonTypeNull: { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, 4)); - printBufPutString(printfBuf, (uint8_t *)"null", 4); - return RyanJsonTrue; - } - case RyanJsonTypeBool: { - if (RyanJsonGetBoolValue(pJson)) - { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, 4)); - printBufPutString(printfBuf, (uint8_t *)"true", 4); - } - else - { - RyanJsonCheckReturnFalse(printBufAppend(printfBuf, 5)); - printBufPutString(printfBuf, (uint8_t *)"false", 5); - } - return RyanJsonTrue; - } - case RyanJsonTypeNumber: return RyanJsonPrintNumber(pJson, printfBuf); - case RyanJsonTypeString: return RyanJsonPrintString(pJson, printfBuf); - case RyanJsonTypeArray: return RyanJsonPrintArray(pJson, printfBuf, depth, format); - case RyanJsonTypeObject: return RyanJsonPrintObject(pJson, printfBuf, depth, format); - } - return RyanJsonFalse; -} - -/** - * @brief 将json对象转换为字符串 - * - * @param pJson - * @param preset 对json对象转为字符串后长度的猜测,如果猜测的接近可以减少内存分配次数,提高转换效率 - * @param format 是否格式化 - * @param len 可以通过指针来获取转换后的长度 - * @return char* NULL失败 - */ -char *RyanJsonPrint(RyanJson_t pJson, uint32_t preset, RyanJsonBool_e format, uint32_t *len) -{ - RyanJsonCheckReturnNull(NULL != pJson); - - RyanJsonPrintBuffer printfBuf = { - .isNoAlloc = RyanJsonFalse, - .size = preset, - .cursor = 0, - }; - - if (printfBuf.size < RyanJsonPrintfPreAlloSize) { printfBuf.size = RyanJsonPrintfPreAlloSize; } - printfBuf.bufAddress = (uint8_t *)jsonMalloc(printfBuf.size); - RyanJsonCheckReturnNull(NULL != printfBuf.bufAddress); - - RyanJsonCheckCode(RyanJsonTrue == RyanJsonPrintValue(pJson, &printfBuf, 0, format), { - jsonFree(printfBuf.bufAddress); - return NULL; - }); - - RyanJsonCheckCode(printBufAppend(&printfBuf, 1), { - jsonFree(printfBuf.bufAddress); - return NULL; - }); - - printfBuf.bufAddress[printfBuf.cursor] = '\0'; - if (len) { *len = printfBuf.cursor; } - - return (char *)printfBuf.bufAddress; -} - -/** - * @brief 使用给定缓冲区将json对象转换为字符串 - * - * @param pJson - * @param buffer 用户给定缓冲区地址 - * @param length 缓冲区长度 - * @param format - * @param len - * @return char* - */ -char *RyanJsonPrintPreallocated(RyanJson_t pJson, char *buffer, uint32_t length, RyanJsonBool_e format, uint32_t *len) -{ - RyanJsonCheckReturnNull(NULL != pJson && NULL != buffer); - - RyanJsonPrintBuffer printfBuf = { - .bufAddress = (uint8_t *)buffer, - .isNoAlloc = RyanJsonTrue, - .size = length, - .cursor = 0, - }; - - RyanJsonCheckReturnNull(RyanJsonTrue == RyanJsonPrintValue(pJson, &printfBuf, 0, format)); - - RyanJsonCheckReturnNull(printBufAppend(&printfBuf, 1)); - printfBuf.bufAddress[printfBuf.cursor] = '\0'; - if (len) { *len = printfBuf.cursor; } - - return (char *)printfBuf.bufAddress; -} - -/** - * @brief 获取 json 的子项个数 - * - * @param pJson - * @return uint32_t - */ -uint32_t RyanJsonGetSize(RyanJson_t pJson) -{ - RyanJsonCheckCode(NULL != pJson, { return 0; }); - - if (!_checkType(RyanJsonGetType(pJson), RyanJsonTypeArray) && !_checkType(RyanJsonGetType(pJson), RyanJsonTypeObject)) { return 1; } - - RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); - uint32_t size = 0; - while (NULL != nextItem) - { - size++; - nextItem = nextItem->next; - } - - return size; -} - -/** - * @brief 通过 索引 获取json对象的子项 - * - * @param pJson - * @param index - * @return RyanJson_t - */ -RyanJson_t RyanJsonGetObjectByIndex(RyanJson_t pJson, uint32_t index) -{ - RyanJsonCheckReturnNull(NULL != pJson); - - RyanJsonCheckReturnNull(_checkType(RyanJsonGetType(pJson), RyanJsonTypeArray) || - _checkType(RyanJsonGetType(pJson), RyanJsonTypeObject)); - - RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); - RyanJsonCheckReturnNull(NULL != nextItem); - while (index > 0) - { - index--; - nextItem = nextItem->next; - RyanJsonCheckReturnNull(NULL != nextItem); - } - - return nextItem; -} - -/** - * @brief 通过 key 获取json对象的子项 - * - * @param pJson - * @param key - * @return RyanJson_t - */ -RyanJson_t RyanJsonGetObjectByKey(RyanJson_t pJson, const char *key) -{ - RyanJsonCheckReturnNull(NULL != pJson && NULL != key); - - RyanJsonCheckReturnNull(_checkType(RyanJsonGetType(pJson), RyanJsonTypeObject)); - - RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); - RyanJsonCheckReturnNull(NULL != nextItem && RyanJsonIsKey(nextItem)); - - while (0 != RyanJsonStrcmp(RyanJsonGetKey(nextItem), key)) - { - nextItem = nextItem->next; - RyanJsonCheckReturnNull(NULL != nextItem && RyanJsonIsKey(nextItem)); - } - - return nextItem; -} - -/** - * @brief 通过 索引 分离json对象的子项 - * - * @param pJson - * @param index - * @return RyanJson_t 被分离对象的指针 - */ -RyanJson_t RyanJsonDetachByIndex(RyanJson_t pJson, uint32_t index) -{ - RyanJsonCheckReturnNull(NULL != pJson); - - RyanJsonCheckReturnNull(_checkType(RyanJsonGetType(pJson), RyanJsonTypeArray) || - _checkType(RyanJsonGetType(pJson), RyanJsonTypeObject)); - - RyanJson_t prev = NULL; - RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); - RyanJsonCheckReturnNull(NULL != nextItem); - - while (index > 0) - { - prev = nextItem; - nextItem = nextItem->next; - index--; - RyanJsonCheckReturnNull(NULL != nextItem); - } - - if (NULL != prev) { prev->next = nextItem->next; } - else - { - RyanJsonGetObjectValue(pJson) = nextItem->next; - } - - nextItem->next = NULL; - - return nextItem; -} - -/** - * @brief 通过 key 分离json对象的子项 - * - * @param pJson - * @param key - * @return RyanJson_t - */ -RyanJson_t RyanJsonDetachByKey(RyanJson_t pJson, const char *key) -{ - RyanJsonCheckReturnNull(NULL != pJson && NULL != key); - RyanJsonCheckReturnNull(_checkType(RyanJsonGetType(pJson), RyanJsonTypeObject)); - - RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); - RyanJsonCheckReturnNull(NULL != nextItem && RyanJsonIsKey(nextItem)); - - RyanJson_t prev = NULL; - while (0 != RyanJsonStrcmp(RyanJsonGetKey(nextItem), key)) - { - prev = nextItem; - nextItem = nextItem->next; - RyanJsonCheckReturnNull(NULL != nextItem && RyanJsonIsKey(nextItem)); - } - - if (NULL != prev) { prev->next = nextItem->next; } - else // 更改的可能是第一个节点 - { - RyanJsonGetObjectValue(pJson) = nextItem->next; - } - - nextItem->next = NULL; - - return nextItem; -} +#include "RyanJsonInternal.h" /** - * @brief 通过 索引 删除json对象的子项 - * - * @param pJson - * @param index - * @return RyanJsonBool_e + * @brief 全局内存钩子。 + * @note 由 RyanJsonInitHooks 在运行前初始化。 */ -RyanJsonBool_e RyanJsonDeleteByIndex(RyanJson_t pJson, uint32_t index) -{ - RyanJsonCheckReturnFalse(NULL != pJson); - - RyanJson_t nextItem = RyanJsonDetachByIndex(pJson, index); - RyanJsonCheckReturnFalse(NULL != nextItem); - - RyanJsonDelete(nextItem); - return RyanJsonTrue; -} +RyanJsonMalloc_t jsonMalloc = NULL; +RyanJsonFree_t jsonFree = NULL; +RyanJsonRealloc_t jsonRealloc = NULL; /** - * @brief 通过 key 删除json对象的子项 + * @brief 初始化内存钩子(malloc/free/realloc) * - * @param pJson - * @param key - * @return RyanJsonBool_e + * @param userMalloc 用户自定义 malloc + * @param userFree 用户自定义 free + * @param userRealloc 用户自定义 realloc,可为 NULL + * @return RyanJsonBool_e 初始化是否成功 */ -RyanJsonBool_e RyanJsonDeleteByKey(RyanJson_t pJson, const char *key) +RyanJsonBool_e RyanJsonInitHooks(RyanJsonMalloc_t userMalloc, RyanJsonFree_t userFree, RyanJsonRealloc_t userRealloc) { - RyanJsonCheckReturnFalse(NULL != pJson && NULL != key); - - RyanJson_t nextItem = RyanJsonDetachByKey(pJson, key); - RyanJsonCheckReturnFalse(NULL != nextItem); + RyanJsonCheckReturnFalse(NULL != userMalloc && NULL != userFree); - RyanJsonDelete(nextItem); + jsonMalloc = userMalloc; + jsonFree = userFree; + jsonRealloc = userRealloc; return RyanJsonTrue; } /** - * @brief 按 索引 插入json对象 + * @brief 释放 RyanJson 动态分配的内存块 * - * @param pJson - * @param index - * @param item - * @return RyanJsonBool_e + * @param block 待释放内存 */ -RyanJsonBool_e RyanJsonInsert(RyanJson_t pJson, uint32_t index, RyanJson_t item) -{ - RyanJsonCheckReturnFalse(NULL != item); - RyanJsonCheckCode(NULL != pJson, { goto __exit; }); - - RyanJsonCheckCode(_checkType(RyanJsonGetType(pJson), RyanJsonTypeArray) || - (_checkType(RyanJsonGetType(pJson), RyanJsonTypeObject) && RyanJsonIsKey(item)), - { - jsonLog("__error 不是正确类型 %d\r\n", index); - goto __exit; - }); - - RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); - RyanJson_t prev = NULL; - - while (nextItem && index > 0) - { - prev = nextItem; - nextItem = nextItem->next; - index--; - } - - if (NULL != prev) { prev->next = item; } - else - { - RyanJsonGetObjectValue(pJson) = item; - } - - // nextItem为NULL时这样赋值也是可以的 - item->next = nextItem; - - return RyanJsonTrue; - -__exit: - RyanJsonDelete(item); - return RyanJsonFalse; -} - -RyanJsonBool_e RyanJsonAddItemToObject(RyanJson_t pJson, const char *key, RyanJson_t item) +void RyanJsonFree(void *block) { - RyanJsonCheckReturnFalse(NULL != pJson); - - RyanJson_t pItem = RyanJsonCreateItem(key, item); - RyanJsonCheckCode(NULL != pItem, { - RyanJsonDelete(item); - return RyanJsonFalse; - }); - - return RyanJsonInsert(pJson, UINT32_MAX, pItem); + jsonFree(block); } /** - * @brief 替换json对象节点 + * @brief 扩容内存块(优先使用 hooks 中的 realloc) * - * @param prev - * @param oldItem - * @param newItem - * @return RyanJsonBool_e + * @param block 原始内存块 + * @param oldSize 原始大小 + * @param newSize 新大小 + * @return void* 扩容后的内存地址,失败返回 NULL */ -static RyanJsonBool_e RyanJsonReplaceNode(RyanJson_t prev, RyanJson_t oldItem, RyanJson_t newItem) +RyanJsonInternalApi void *RyanJsonInternalExpandRealloc(void *block, uint32_t oldSize, uint32_t newSize) { - RyanJsonCheckAssert(NULL != oldItem && NULL != newItem); - - // 链接前驱和新节点 - if (NULL != prev) { prev->next = newItem; } + // 不考虑 block 为空的情况 + RyanJsonCheckAssert(NULL != block); + if (NULL != jsonRealloc) { return jsonRealloc(block, newSize); } - // 链接后继和新节点 - if (NULL != oldItem->next) { newItem->next = oldItem->next; } + void *newBlock = jsonMalloc(newSize); + RyanJsonCheckReturnNull(NULL != newBlock); - oldItem->next = NULL; - return RyanJsonTrue; + RyanJsonMemcpy(newBlock, block, oldSize); + jsonFree(block); + return newBlock; } /** - * @brief 通过 索引 替换json对象的子项 + * @brief 删除 Json 树并释放所有资源 * - * @param pJson - * @param index - * @param item - * @return RyanJsonBool_e + * @param pJson 待删除的根节点 */ -RyanJsonBool_e RyanJsonReplaceByIndex(RyanJson_t pJson, uint32_t index, RyanJson_t item) +void RyanJsonDelete(RyanJson_t pJson) { - RyanJsonCheckReturnFalse(NULL != pJson && NULL != item); - - RyanJsonCheckReturnFalse(_checkType(RyanJsonGetType(pJson), RyanJsonTypeArray) || - _checkType(RyanJsonGetType(pJson), RyanJsonTypeObject)); - - RyanJson_t prev = NULL; - RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); - RyanJsonCheckReturnFalse(NULL != nextItem); + RyanJsonCheckCode(NULL != pJson, { return; }); - // 查找子项 - while (index > 0) + RyanJson_t current = pJson; + RyanJson_t nextNode; + while (NULL != current) { - prev = nextItem; - nextItem = nextItem->next; - index--; - RyanJsonCheckReturnFalse(NULL != nextItem); - } + // 容器优先下沉:如果有子节点,先剥离并优先处理子节点 + if (_checkType(current, RyanJsonTypeArray) || _checkType(current, RyanJsonTypeObject)) + { + nextNode = RyanJsonGetObjectValue(current); + if (nextNode) + { + RyanJsonInternalChangeObjectValue(current, NULL); // 断开子节点连接,防止循环回溯 + current = nextNode; + continue; + } + } - RyanJsonReplaceNode(prev, nextItem, item); - if (NULL == prev) { RyanJsonGetObjectValue(pJson) = item; } + // 确定后续节点:根节点结束;非根节点通过 next 串联待处理路径 + // 注意:这里不能用 RyanJsonGetNext,因为 Last 节点会返回 NULL, + // 但删除流程需要利用线索 next 回溯到父节点 + nextNode = (current == pJson) ? NULL : current->next; - RyanJsonDelete(nextItem); - return RyanJsonTrue; + // 释放当前节点资源 + // 如果 strValue 区采用指针模式存储,需先释放外部堆空间 + if (RyanJsonTrue == RyanJsonGetPayloadStrIsPtrByFlag(current)) { jsonFree(RyanJsonInternalGetStrPtrModeBuf(current)); } + jsonFree(current); + current = nextNode; + } } /** - * @brief 通过 key 替换json对象的子项 + * @brief 获取节点规模(标量为1,容器为子节点个数) * - * @param pJson - * @param key - * @param item - * @return RyanJsonBool_e + * @param pJson 待查询节点 + * @return uint32_t 元素数量;参数非法返回 0 */ -RyanJsonBool_e RyanJsonReplaceByKey(RyanJson_t pJson, const char *key, RyanJson_t item) +uint32_t RyanJsonGetSize(RyanJson_t pJson) { - RyanJsonCheckReturnFalse(NULL != pJson && NULL != key && NULL != item); + RyanJsonCheckCode(NULL != pJson, { return 0; }); - RyanJsonCheckReturnFalse(_checkType(RyanJsonGetType(pJson), RyanJsonTypeObject)); + if (!_checkType(pJson, RyanJsonTypeArray) && !_checkType(pJson, RyanJsonTypeObject)) { return 1; } - RyanJson_t prev = NULL; - // todo 增加nextItem没有key的测试 RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); - RyanJsonCheckReturnFalse(NULL != nextItem && RyanJsonIsKey(nextItem)); - - // 找到要修改的节点 - while (0 != RyanJsonStrcmp(RyanJsonGetKey(nextItem), key)) - { - prev = nextItem; - nextItem = nextItem->next; - RyanJsonCheckReturnFalse(NULL != nextItem && RyanJsonIsKey(nextItem)); - } - - // 没有key的对象 申请一个带key的对象 - if (RyanJsonFalse == RyanJsonIsKey(item)) - { - item = RyanJsonCreateItem(key, item); - RyanJsonCheckReturnFalse(NULL != item); - } - else + uint32_t size = 0; + while (NULL != nextItem) { - if (0 != RyanJsonStrcmp(RyanJsonGetKey(item), key)) { RyanJsonChangeKey(item, key); } + size++; + nextItem = RyanJsonGetNext(nextItem); } - RyanJsonReplaceNode(prev, nextItem, item); - if (NULL == prev) { RyanJsonGetObjectValue(pJson) = item; } - - RyanJsonDelete(nextItem); - - return RyanJsonTrue; -} - -RyanJsonBool_e RyanJsonChangeKey(RyanJson_t pJson, const char *key) -{ - RyanJsonCheckReturnFalse(NULL != pJson && NULL != key); - RyanJsonCheckReturnFalse(RyanJsonIsKey(pJson) || RyanJsonIsString(pJson)); - return RyanJsonChangeString(pJson, RyanJsonFalse, key, RyanJsonIsString(pJson) ? RyanJsonGetStringValue(pJson) : NULL); -} - -RyanJsonBool_e RyanJsonChangeStringValue(RyanJson_t pJson, const char *strValue) -{ - RyanJsonCheckReturnFalse(NULL != pJson && NULL != strValue); - RyanJsonCheckReturnFalse(RyanJsonIsKey(pJson) || RyanJsonIsString(pJson)); - return RyanJsonChangeString(pJson, RyanJsonFalse, RyanJsonIsKey(pJson) ? RyanJsonGetKey(pJson) : NULL, strValue); -} - -/** - * @brief 创建一个 NULL 类型的json对象 - * - * @param key - * @return RyanJson_t - */ -RyanJson_t RyanJsonCreateNull(const char *key) -{ - RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeNull, .key = key}; - return RyanJsonNewNode(&nodeInfo); -} - -/** - * @brief 创建一个 boolean 类型的json对象 - * - * @param key - * @return RyanJson_t - */ -RyanJson_t RyanJsonCreateBool(const char *key, RyanJsonBool_e boolean) -{ - RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeBool, .key = key, .boolIsTrueFlag = boolean}; - return RyanJsonNewNode(&nodeInfo); -} - -/** - * @brief 创建一个 number 类型中的 int32_t 类型json对象 - * - * @param key - * @return RyanJson_t - */ -RyanJson_t RyanJsonCreateInt(const char *key, int32_t number) -{ - RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeNumber, .key = key, .numberIsDoubleFlag = RyanJsonFalse}; - - RyanJson_t item = RyanJsonNewNode(&nodeInfo); - RyanJsonCheckReturnNull(NULL != item); - - RyanJsonGetIntValue(item) = number; - return item; -} - -/** - * @brief 创建一个 number 类型中的 double 类型json对象 - * 可以使用double来保存int64类型数据,但是更推荐通过字符串保存 - * @param key - * @param number - * @return RyanJson_t - */ -RyanJson_t RyanJsonCreateDouble(const char *key, double number) -{ - RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeNumber, .key = key, .numberIsDoubleFlag = RyanJsonTrue}; - RyanJson_t item = RyanJsonNewNode(&nodeInfo); - RyanJsonCheckReturnNull(NULL != item); - - RyanJsonGetDoubleValue(item) = number; - return item; -} - -/** - * @brief 创建一个 string 类型的json对象 - * - * @param key - * @param string - * @return RyanJson_t - */ -RyanJson_t RyanJsonCreateString(const char *key, const char *string) -{ - RyanJsonCheckReturnNull(NULL != string); - - RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeString, .key = key, .strValue = string}; - return RyanJsonNewNode(&nodeInfo); -} - -/** - * @brief 创建一个 obj 类型的json对象 - * - * @return RyanJson_t - */ -static RyanJson_t RyanJsonCreateObjectAndKey(const char *key) -{ - RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeObject, .key = key}; - return RyanJsonNewNode(&nodeInfo); -} - -RyanJson_t RyanJsonCreateObject(void) { return RyanJsonCreateObjectAndKey(NULL); } - -/** - * @brief 创建一个 arr 类型的json对象 - * - * @return RyanJson_t - */ -static RyanJson_t RyanJsonCreateArrayAndKey(const char *key) -{ - RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeArray, .key = key}; - return RyanJsonNewNode(&nodeInfo); + return size; } -RyanJson_t RyanJsonCreateArray(void) { return RyanJsonCreateArrayAndKey(NULL); } /** - * @brief 创建一个item对象 - * 带有key的对象才可以方便的通过replace替换, - * !此接口不推荐用户调用 + * @brief 复制单个节点(不递归复制子节点) * - * @param key - * @param item - * @return RyanJson_t + * @param pJson 源节点 + * @return RyanJson_t 新节点,失败返回 NULL */ -RyanJson_t RyanJsonCreateItem(const char *key, RyanJson_t item) +static RyanJson_t RyanJsonDuplicateNode(RyanJson_t pJson) { - RyanJsonCheckReturnNull(NULL != item); - - RyanJsonNodeInfo_t nodeInfo = { - .type = _checkType(RyanJsonGetType(item), RyanJsonTypeArray) ? RyanJsonTypeArray : RyanJsonTypeObject, - .key = key, - }; - - RyanJson_t newItem = RyanJsonNewNode(&nodeInfo); + RyanJsonCheckAssert(NULL != pJson); - RyanJsonCheckReturnNull(NULL != newItem); + char *key = RyanJsonIsKey(pJson) ? RyanJsonGetKey(pJson) : NULL; + RyanJson_t newItem = NULL; - if (_checkType(RyanJsonGetType(item), RyanJsonTypeArray) || _checkType(RyanJsonGetType(item), RyanJsonTypeObject)) + switch (RyanJsonGetType(pJson)) { - RyanJsonGetObjectValue(newItem) = RyanJsonGetObjectValue(item); + case RyanJsonTypeNull: newItem = RyanJsonCreateNull(key); break; + case RyanJsonTypeBool: // 创建节点时已写入 bool 值 + newItem = RyanJsonCreateBool(key, RyanJsonGetBoolValue(pJson)); + break; - if (RyanJsonIsKey(item) || RyanJsonIsString(item)) { jsonFree(RyanJsonGetHiddePrt(item)); } - jsonFree(item); - } - else - { - RyanJsonGetObjectValue(newItem) = item; + case RyanJsonTypeNumber: + if (RyanJsonIsInt(pJson)) { newItem = RyanJsonCreateInt(key, RyanJsonGetIntValue(pJson)); } + else + { + // Number 节点除了 int32_t 只可能是 double + RyanJsonCheckAssert(RyanJsonIsDouble(pJson)); + newItem = RyanJsonCreateDouble(key, RyanJsonGetDoubleValue(pJson)); + } + break; + case RyanJsonTypeString: newItem = RyanJsonCreateString(key, RyanJsonGetStringValue(pJson)); break; + case RyanJsonTypeArray: newItem = RyanJsonInternalCreateArrayAndKey(key); break; + case RyanJsonTypeObject: newItem = RyanJsonInternalCreateObjectAndKey(key); break; } - return newItem; } -RyanJsonBool_e RyanJsonIsKey(RyanJson_t pJson) { return RyanJsonMakeBool(NULL != pJson && RyanJsonGetPayloadWhiteKeyByFlag(pJson)); } -RyanJsonBool_e RyanJsonIsNull(RyanJson_t pJson) { return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeNull == RyanJsonGetType(pJson)); } -RyanJsonBool_e RyanJsonIsBool(RyanJson_t pJson) { return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeBool == RyanJsonGetType(pJson)); } -RyanJsonBool_e RyanJsonIsNumber(RyanJson_t pJson) -{ return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeNumber == RyanJsonGetType(pJson)); } -RyanJsonBool_e RyanJsonIsString(RyanJson_t pJson) -{ return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeString == RyanJsonGetType(pJson)); } -RyanJsonBool_e RyanJsonIsArray(RyanJson_t pJson) { return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeArray == RyanJsonGetType(pJson)); } -RyanJsonBool_e RyanJsonIsObject(RyanJson_t pJson) -{ return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeObject == RyanJsonGetType(pJson)); } -RyanJsonBool_e RyanJsonIsInt(RyanJson_t pJson) -{ return RyanJsonMakeBool(RyanJsonIsNumber(pJson) && (RyanJsonFalse == RyanJsonGetPayloadNumberIsDoubleByFlag(pJson))); } -RyanJsonBool_e RyanJsonIsDouble(RyanJson_t pJson) -{ return RyanJsonMakeBool(RyanJsonIsNumber(pJson) && (RyanJsonTrue == RyanJsonGetPayloadNumberIsDoubleByFlag(pJson))); } - /** - * @brief 深拷贝一份json对象 + * @brief 深拷贝整棵 Json 树(迭代版) * - * @param pJson - * @return RyanJson_t 拷贝的新对象指针 + * @param pJson 源 Json 根节点 + * @return RyanJson_t 拷贝后的根节点,失败返回 NULL */ RyanJson_t RyanJsonDuplicate(RyanJson_t pJson) { RyanJsonCheckReturnNull(NULL != pJson); - char *key = NULL; - if (RyanJsonIsKey(pJson)) { key = RyanJsonGetKey(pJson); } - - RyanJson_t newItem = NULL; - switch (RyanJsonGetType(pJson)) - { - case RyanJsonTypeNull: newItem = RyanJsonCreateNull(key); break; - case RyanJsonTypeBool: // 创建NewNode的时候已经赋值了 - newItem = RyanJsonCreateBool(key, RyanJsonGetBoolValue(pJson)); - break; + // 先复制根节点 + RyanJson_t root = RyanJsonDuplicateNode(pJson); + RyanJsonCheckReturnNull(NULL != root); - case RyanJsonTypeNumber: - // 不可能出现另外一个条件了 - if (RyanJsonIsInt(pJson)) { newItem = RyanJsonCreateInt(key, RyanJsonGetIntValue(pJson)); } - else if (RyanJsonIsDouble(pJson)) { newItem = RyanJsonCreateDouble(key, RyanJsonGetDoubleValue(pJson)); } - break; + // 初始化迭代状态 + // sourceNode:当前遍历到的源节点,初始指向根节点的首个子节点 + RyanJson_t sourceNode = NULL; - case RyanJsonTypeString: newItem = RyanJsonCreateString(key, RyanJsonGetStringValue(pJson)); break; + if (_checkType(pJson, RyanJsonTypeArray) || _checkType(pJson, RyanJsonTypeObject)) { sourceNode = RyanJsonGetObjectValue(pJson); } - case RyanJsonTypeArray: - case RyanJsonTypeObject: { - if (_checkType(RyanJsonGetType(pJson), RyanJsonTypeArray)) { newItem = RyanJsonCreateArrayAndKey(key); } - else - { - newItem = RyanJsonCreateObjectAndKey(key); - } + // 如果根节点不是容器类型(Array 或 Object),或者容器为空(没有子节点), + // 则不需要进行后续的子节点复制,直接返回根节点副本即可。 + // RyanJsonGetObjectValue 返回容器的子节点起始指针。 + if (NULL == sourceNode) { return root; } - RyanJsonCheckCode(NULL != newItem, { goto err; }); + // targetParent:目标树中当前插入位置的父节点 + RyanJson_t targetParent = root; + // lastSibling:目标树当前层级里最近插入的兄弟节点 + RyanJson_t lastSibling = NULL; - RyanJson_t item, prev = NULL; - RyanJson_t temp = RyanJsonGetObjectValue(pJson); - while (temp) + while (1) + { + // 复制当前节点并插入目标树 { - item = RyanJsonDuplicate(temp); - RyanJsonCheckCode(NULL != item, { goto err; }); + RyanJson_t newItem = RyanJsonDuplicateNode(sourceNode); + RyanJsonCheckCode(NULL != newItem, { goto error__; }); - // RyanJsonCheckAssert(RyanJsonTrue == RyanJsonInsert(newItem, UINT32_MAX, item)); + // 新节点插入到目标父节点下,位置在 lastSibling 之后 + // RyanJsonInternalListInsertAfter 会维护链表连接与 IsLast 标记 + RyanJsonInternalListInsertAfter(targetParent, lastSibling, newItem); + lastSibling = newItem; // 更新 lastSibling 为刚插入的节点 + } - if (NULL != prev) + // 当前节点是非空容器时下沉到子层 + // 如果当前源节点是容器且非空,则进入下一层级 + if (_checkType(sourceNode, RyanJsonTypeArray) || _checkType(sourceNode, RyanJsonTypeObject)) + { + RyanJson_t child = RyanJsonGetObjectValue(sourceNode); + if (child) { - prev->next = item; - prev = item; + sourceNode = child; // 移动到子节点 + targetParent = lastSibling; // 上一步插入的新节点成为下一层的父节点 + lastSibling = NULL; // 新层级尚未插入子节点 + continue; // 继续循环处理子节点 } - else + } + + // 同层前进,必要时回溯到父层 + while (1) + { + // 优先尝试移动到下一个兄弟节点 + RyanJson_t next = RyanJsonGetNext(sourceNode); + if (next) { - RyanJsonGetObjectValue(newItem) = item; - prev = item; + sourceNode = next; // 移动到兄弟节点 + break; // 跳出内层循环,回到外层循环进行复制 } - temp = temp->next; - } - break; - } + // 无兄弟节点(当前为 Last)时沿线索回溯到父节点 + // 线索化链表中,IsLast=1 的节点其 next 指向父节点 + sourceNode = sourceNode->next; - default: goto err; - } + // 回溯到起始根节点,说明整棵树遍历完成 + if (sourceNode == pJson) { return root; } - return newItem; + // 目标树同步回溯到上一层 + // 此时 targetParent 是当前层的父节点,它的所有子节点都已复制完毕。 + // 我们需要回到 targetParent 的父节点层级。 + // 在进入这一层时,targetParent 是作为 lastSibling 被记录的。 + // 也就是:上一层的 lastSibling 就是现在的 targetParent。 + lastSibling = targetParent; -err: - RyanJsonDelete(newItem); + // 同步遍历下 targetParent 也一定是 Last 节点, + // 因而它的 next 同样指向父节点。 + targetParent = targetParent->next; + } + } + +error__: + RyanJsonDelete(root); return NULL; } /** - * @brief 通过删除无效字符、注释等, 减少json文本大小 - * - * @param text 文本指针 - */ - -/** - * @brief 通过删除无效字符、注释等, 减少json文本大小 + * @brief 原地压缩 Json 字符串(移除空白与注释) * - * @param text 文本指针 - * @param textLen 文本长度,使用int32_t是方式用户隐士转换不好发现bug - * @return uint32_t + * @param text 可写缓冲区 + * @param textLen 缓冲区可写长度 + * @return uint32_t 压缩后字符数(不含终止符) + * @note 仅当返回值小于 textLen 时写入 '\0' */ uint32_t RyanJsonMinify(char *text, int32_t textLen) { RyanJsonCheckCode(NULL != text && textLen > 0, { return 0; }); - char *t = (char *)text; // 假设 text 指向可写缓冲区 + char *t = (char *)text; // 写指针 const char *end = text + textLen; // 边界 uint32_t count = 0; // 压缩后字符数 while (text < end && *text) { - if (*text == ' ' || *text == '\t' || *text == '\r' || *text == '\n') { text++; } - else if (*text == '/' && (text + 1 < end) && text[1] == '/') + if (' ' == *text || '\t' == *text || '\r' == *text || '\n' == *text) { text++; } + else if ('/' == *text && (text + 1 < end) && '/' == text[1]) { - while (text < end && *text && *text != '\n') { text++; } + while (text < end && *text && '\n' != *text) + { + text++; + } } - else if (*text == '/' && (text + 1 < end) && text[1] == '*') + else if ('/' == *text && (text + 1 < end) && '*' == text[1]) { text += 2; - while (text < end && *text && !(*text == '*' && (text + 1 < end) && text[1] == '/')) { text++; } + while (text < end && *text && !('*' == *text && (text + 1 < end) && '/' == text[1])) + { + text++; + } if (text + 1 < end) { text += 2; } } - else if (*text == '\"') + else if ('\"' == *text) { *t++ = *text++; count++; - while (text < end && *text && *text != '\"') + while (text < end && *text && '\"' != *text) { - if (*text == '\\' && text + 1 < end) + if ('\\' == *text && text + 1 < end) { *t++ = *text++; count++; @@ -2133,80 +310,173 @@ uint32_t RyanJsonMinify(char *text, int32_t textLen) } } - *t = '\0'; // 调用者需保证缓冲区有空间 + // 仅当缓冲区仍有空间时写入终止符,避免越界写 + if (t < end) { *t = '\0'; } return count; // 返回压缩后大小 } /** - * @brief 递归比较两个 pJson 对象key和value是否相等。 - * 此接口效率较低, 谨慎使用 - * @param leftJson - * @param rightJson - * @return RyanJsonBool_e + * @brief Json 内部比较函数(支持全量比较/仅 Key 比较) + * + * @param leftJson 左侧节点 + * @param rightJson 右侧节点 + * @param fullCompare RyanJsonTrue 比较值,RyanJsonFalse 仅比较结构与 key + * @return RyanJsonBool_e 两棵树是否相等 */ -RyanJsonBool_e RyanJsonCompare(RyanJson_t leftJson, RyanJson_t rightJson) +static RyanJsonBool_e RyanJsonInternalCompare(RyanJson_t leftJson, RyanJson_t rightJson, RyanJsonBool_e fullCompare) { - if (NULL == leftJson || NULL == rightJson) { return RyanJsonFalse; } + RyanJsonCheckReturnFalse(NULL != leftJson && NULL != rightJson); - // 相同的对象相等 if (leftJson == rightJson) { return RyanJsonTrue; } - if (RyanJsonGetType(leftJson) != RyanJsonGetType(rightJson)) { return RyanJsonFalse; } + RyanJson_t leftCurrent = leftJson; + RyanJson_t rightCurrent = rightJson; - switch (RyanJsonGetType(leftJson)) + while (1) { - case RyanJsonTypeNull: return RyanJsonTrue; - - case RyanJsonTypeBool: return RyanJsonGetBoolValue(leftJson) == RyanJsonGetBoolValue(rightJson) ? RyanJsonTrue : RyanJsonFalse; + // 比较当前节点的类型、值与规模 + RyanJsonCheckReturnFalse(RyanJsonGetType(leftCurrent) == RyanJsonGetType(rightCurrent)); - case RyanJsonTypeNumber: { - - if (RyanJsonTrue == RyanJsonIsInt(leftJson) && RyanJsonTrue == RyanJsonIsInt(rightJson)) + switch (RyanJsonGetType(leftCurrent)) { - return (RyanJsonGetIntValue(leftJson) == RyanJsonGetIntValue(rightJson)) ? RyanJsonTrue : RyanJsonFalse; + case RyanJsonTypeNull: break; + case RyanJsonTypeBool: + if (fullCompare) + { + RyanJsonCheckReturnFalse(RyanJsonGetBoolValue(leftCurrent) == RyanJsonGetBoolValue(rightCurrent)); + } + break; + case RyanJsonTypeNumber: + if (fullCompare) + { + if (RyanJsonIsInt(leftCurrent)) + { + RyanJsonCheckReturnFalse(RyanJsonGetIntValue(leftCurrent) == RyanJsonGetIntValue(rightCurrent)); + } + else + { + RyanJsonCheckReturnFalse(RyanJsonCompareDouble(RyanJsonGetDoubleValue(leftCurrent), + RyanJsonGetDoubleValue(rightCurrent))); + } + } + break; + case RyanJsonTypeString: + if (fullCompare) + { + RyanJsonCheckReturnFalse( + RyanJsonInternalStrEq(RyanJsonGetStringValue(leftCurrent), RyanJsonGetStringValue(rightCurrent))); + } + break; + case RyanJsonTypeArray: + case RyanJsonTypeObject: RyanJsonCheckReturnFalse(RyanJsonGetSize(leftCurrent) == RyanJsonGetSize(rightCurrent)); break; + default: return RyanJsonFalse; } - if (RyanJsonTrue == RyanJsonIsDouble(leftJson) && RyanJsonTrue == RyanJsonIsDouble(rightJson)) + // 容器节点尝试下沉到子节点继续比较 + if (_checkType(leftCurrent, RyanJsonTypeArray) || _checkType(leftCurrent, RyanJsonTypeObject)) { - return compare_double(RyanJsonGetDoubleValue(leftJson), RyanJsonGetDoubleValue(rightJson)); - } + RyanJson_t leftChild = RyanJsonGetObjectValue(leftCurrent); + if (leftChild) + { + RyanJson_t rightChild = NULL; - return RyanJsonFalse; - } + if (RyanJsonIsArray(leftCurrent)) { rightChild = RyanJsonGetObjectValue(rightCurrent); } + else + { + RyanJsonCheckAssert(RyanJsonTrue == RyanJsonIsKey(leftChild)); + const char *leftChildKey = RyanJsonGetKey(leftChild); + + // 同序快路径:首子节点 key 一致时直接下沉,避免一次 O(n) 查找 + RyanJson_t rightFirstChild = RyanJsonGetObjectValue(rightCurrent); + RyanJsonCheckAssert(NULL != rightFirstChild && RyanJsonTrue == RyanJsonIsKey(rightFirstChild)); + if (RyanJsonTrue == RyanJsonInternalStrEq(leftChildKey, RyanJsonGetKey(rightFirstChild))) + { + rightChild = rightFirstChild; + } + else + { + rightChild = RyanJsonGetObjectByKey(rightCurrent, leftChildKey); + } + } - case RyanJsonTypeString: - return (0 == RyanJsonStrcmp(RyanJsonGetStringValue(leftJson), RyanJsonGetStringValue(rightJson))) ? RyanJsonTrue - : RyanJsonFalse; + RyanJsonCheckReturnFalse(NULL != rightChild); - case RyanJsonTypeArray: { - if (RyanJsonGetSize(leftJson) != RyanJsonGetSize(rightJson)) { return RyanJsonFalse; } + leftCurrent = leftChild; + rightCurrent = rightChild; + continue; + } + } - RyanJson_t item; - uint32_t itemIndex = 0; - RyanJsonArrayForEach(leftJson, item) + // 同层前进,必要时回溯 + while (1) { - if (RyanJsonTrue != RyanJsonCompare(item, RyanJsonGetObjectByIndex(rightJson, itemIndex))) { return RyanJsonFalse; } - itemIndex++; - } + if (leftCurrent == leftJson) { return RyanJsonTrue; } - return RyanJsonTrue; - } + // 优先定位右侧对应的兄弟节点 + RyanJson_t leftNext = RyanJsonGetNext(leftCurrent); + if (leftNext) + { + RyanJson_t rightNext = NULL; + if (RyanJsonFalse == RyanJsonIsKey(leftNext)) { rightNext = RyanJsonGetNext(rightCurrent); } + else + { + RyanJsonCheckAssert(RyanJsonTrue == RyanJsonIsKey(leftNext)); + const char *leftNextKey = RyanJsonGetKey(leftNext); + RyanJson_t rightParent = RyanJsonInternalGetParent(rightCurrent); + + // 同序快路径:优先尝试右侧当前节点的直接兄弟,未命中再回退到按 key 查找 + RyanJson_t rightCandidate = RyanJsonGetNext(rightCurrent); + if (rightCandidate) { RyanJsonCheckAssert(RyanJsonTrue == RyanJsonIsKey(rightCandidate)); } + if (rightCandidate && + RyanJsonTrue == RyanJsonInternalStrEq(leftNextKey, RyanJsonGetKey(rightCandidate))) + { + rightNext = rightCandidate; + } + else + { + rightNext = RyanJsonGetObjectByKey(rightParent, leftNextKey); + } + } - case RyanJsonTypeObject: { - if (RyanJsonGetSize(leftJson) != RyanJsonGetSize(rightJson)) { return RyanJsonFalse; } + RyanJsonCheckReturnFalse(NULL != rightNext); - RyanJson_t item; - RyanJsonObjectForEach(leftJson, item) - { - if (RyanJsonTrue != RyanJsonCompare(item, RyanJsonGetObjectByKey(rightJson, RyanJsonGetKey(item)))) + leftCurrent = leftNext; + rightCurrent = rightNext; + break; + } + + // 无兄弟可比时回溯到父层 + leftCurrent = leftCurrent->next; + + if (RyanJsonIsArray(leftCurrent)) { rightCurrent = rightCurrent->next; } + else { - return RyanJsonFalse; + rightCurrent = RyanJsonInternalGetParent(rightCurrent); } } - - return RyanJsonTrue; - } } +} + +/** + * @brief 比较两个 Json 是否完全相等(结构 + key + value) + * + * @param leftJson 左侧节点 + * @param rightJson 右侧节点 + * @return RyanJsonBool_e 是否相等 + */ +RyanJsonBool_e RyanJsonCompare(RyanJson_t leftJson, RyanJson_t rightJson) +{ + return RyanJsonInternalCompare(leftJson, rightJson, RyanJsonTrue); +} - return RyanJsonFalse; +/** + * @brief 比较两个 Json 的结构与 key 是否相等(忽略 value) + * + * @param leftJson 左侧节点 + * @param rightJson 右侧节点 + * @return RyanJsonBool_e 是否相等 + */ +RyanJsonBool_e RyanJsonCompareOnlyKey(RyanJson_t leftJson, RyanJson_t rightJson) +{ + return RyanJsonInternalCompare(leftJson, rightJson, RyanJsonFalse); } diff --git a/RyanJson/RyanJson.h b/RyanJson/RyanJson.h index 3c07030..8e6d336 100644 --- a/RyanJson/RyanJson.h +++ b/RyanJson/RyanJson.h @@ -1,5 +1,5 @@ -#ifndef __RyanJson__ -#define __RyanJson__ +#ifndef RyanJson +#define RyanJson #ifdef __cplusplus extern "C" { @@ -8,13 +8,13 @@ extern "C" { #include "RyanJsonConfig.h" /** - * @brief Json 错误处理块 - * + * @brief 内部错误检查宏。 + * @note 条件失败时会打印内部日志并执行调用方传入的恢复代码。 */ #define RyanJsonCheckCodeNoReturn(EX, code) \ if (!(EX)) \ { \ - jsonLog("\r\n%s:%d Check failed: %s\n", __FILE__, __LINE__, #EX); \ + jsonLog("\r\n[INTERNAL ERROR] %s:%d: Check failed (%s)\n", __FILE__, __LINE__, #EX); \ code \ } @@ -22,10 +22,22 @@ extern "C" { #define RyanJsonCheckReturnFalse(EX) RyanJsonCheckCode(EX, return RyanJsonFalse;) #define RyanJsonCheckReturnNull(EX) RyanJsonCheckCode(EX, return NULL;) + +/** + * @brief 断言相关宏。 + * @note 未启用 `RyanJsonEnableAssert` 时,`RyanJsonCheckAssert` 不生效。 + */ #ifdef RyanJsonEnableAssert -#define RyanJsonCheckAssert(EX) RyanJsonCheckCode(EX, RyanJsonAssert(NULL && "RyanJsonCheckAssert");) +#define RyanJsonCheckAssert(EX) RyanJsonCheckCode(EX, RyanJsonAssert(NULL &&#EX);) +#define RyanJsonAssertAlwaysEval(EX) \ + do \ + { \ + if (!(EX)) RyanJsonAssert(NULL && #EX); \ + } while (0) #else -#define RyanJsonCheckAssert(EX) (void)(EX) +#define RyanJsonCheckAssert(EX) +// 无论是否开启断言都会“求值”,但只有在开启断言时才会 assert。 保留EX的副作用 +#define RyanJsonAssertAlwaysEval(EX) ((void)(EX)) #endif // Json 的最基础节点,所有 Json 元素都由该节点表示。 @@ -33,15 +45,23 @@ extern "C" { // 其余数据(flag、key、stringValue、numberValue、doubleValue 等)均通过动态内存分配管理。 struct RyanJsonNode { + // 理论上next的低2位也是可以利用起来的 struct RyanJsonNode *next; // 单链表节点指针 - /* - * 在 next 后紧跟一个字节的 flag,用于描述节点的核心信息: + /** + * @brief RyanJson 节点结构体 + * 每个节点由链表连接,包含元数据标识 (Flag) 与动态载荷存储区。 + * + * 内存布局: + * [ next指针 | flag(1字节) | padding/指针空间 | 动态载荷区 ] * - * 位分布如下: + * @brief 节点元数据标识 (Flag) + * 紧跟 next 指针后,利用 1 字节位域描述节点类型及存储状态。 + * + * flag 位分布定义: * bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 * ----------------------------------------------------- - * 保留 KeyLen KeyLen HasKey NumExt Type2 Type1 Type0 + * strMode KeyLen KeyLen HasKey NumExt Type2 Type1 Type0 * * 各位含义: * - bit0-2 : 节点类型 @@ -50,58 +70,87 @@ struct RyanJsonNode * * - bit3 : 扩展位 * Bool 类型:0=false, 1=true - * Number 类型:0=int, 1=double + * Number 类型:0=int32_t(4字节), 1=double(8字节) * - * - bit4 : 是否包含 Key - * 0=无 Key(数组元素) - * 1=有 Key(对象成员) + * - bit4-5 : Key 长度字段字节数 + * 00:无key + * 01:keyLen=1字节 (≤UINT8_MAX) + * 10:keyLen=2字节 (≤UINT16_MAX) + * 11:keyLen=4字节 (≤UINT32_MAX) * - * - bit5-6 : Key 长度字段字节数 - * 00=1字节 (≤255) - * 01=2字节 (≤65535) - * 10=3字节 (≤16M) - * 11=4字节 (≤UINT32_MAX) + * - bit6 : 表示key / strValue 存储模式 + * 0:inline 模式, 1:ptr 模式 * - * - bit7 : 保留位(未来可用于压缩标记、特殊类型等) - */ - - /* - * flag 后若节点包含 key / strValue,则跟随一个指针, - * 指向存储区:[ keyLen | key | stringValue ] - * 其中 keyLen 的大小由 flag 中的长度信息决定(最多 4 字节)。 + * - bit7 : 表示是否为当前链表的最后一位,是的话nexe指针会指向Parent(线索化链表) + * 0:next 指向兄弟节点, 1:next 指向Parent节点 * - * 在指针之后,根据节点类型存储具体数据: - * - null / bool : 由 flag 表示 - * - string : 由上述指针指向 - * - number : 根据 flag 决定存储 int(4字节) 或 double(8字节) - * - object : 动态分配空间存储子节点,链表结构如下: + * @brief 动态载荷存储区 + * 目的: + * - 在保持 API 易用性和稳定性的同时,最大限度减少 malloc 调用次数。 + * - 尤其在嵌入式平台,malloc 代价高昂:不仅有堆头部空间浪费,还会产生内存碎片。 + * - 通过利用结构体内的对齐填充 (Padding) 和指针空间,形成一个灵活的缓冲区。 + * + * 存储策略: + * 利用结构体内存对齐产生的 Padding(如 Flag 后的空隙)以及原本用于存储指针的空间,形成一个缓冲区 + * 若节点包含 key / strValue,则可能有两种方案: + * inline 模式 (小数据优化) + * - 当 (KeyLen + Key + Value) 的总长度 ≤ 阈值时,直接存储在结构体内部。 + * - 阈值计算公式: + * 阈值 = Padding + sizeof(void*) + (malloc头部空间的一半),再向上对齐到字节边界。 + * 举例: + * - 内存对齐:4字节 + * - malloc头部空间:8字节 + * - 可用空间 = 3 (flag后padding) + 4 (指针空间) + 4 (malloc头部一半) + * - 向上对齐后得到阈值12字节 + * - 存储布局: + * [ KeyLen | Key | Value ] + * 起始地址即为 flag 之后,数据紧凑排列,无需额外 malloc。 + * + * ptr 模式 (大数据) + * - 当数据长度 > 阈值时,结构体存储一个指针,指向独立的堆区。 + * - 存储布局: + * [ KeyLen | *ptr | Padding ] -> (ptr指向) [ Key | Value ] + * - KeyLen 的大小由 flag 中的长度字段决定 (最多 4 字节)。 + * - 这样保证大数据不会撑爆结构体,同时保持 API 一致性。 + + * 其他类型的存储: + * - null / bool : 由 flag 位直接表示,无需额外空间。 + * - number : 根据 flag 扩展位决定存储 int32_t(4字节) 或 double(8字节)。 + * - object : 动态分配空间存储子节点,采用链表结构。 + * + * 设计考量: + * - malloc 在嵌入式平台的开销: + * * RTT 最小内存管理算法中,malloc 头部约 12 字节(可以考虑tlsf算法头部空间仅4字节,内存碎片也控制的很好,适合物联网应用)。 + * * 一个 RyanJson 节点本身可能只有个位数字节,头部空间就让内存占用翻倍。 + * - 因此: + * * 小数据尽量 inline 存储,避免二次 malloc。 + * * 大数据 fallback 到 ptr 模式,保证灵活性。 + * - 修改场景: + * * 理想情况:节点结构体后面直接跟 key/strValue,修改时释放并重新申请节点。 + * * 但这样 changKey/changStrValue 接口改动太大,用户层需要修改指针,代价高。 + * * 实际策略:提供就地修改接口。 + * - 若新值长度 ≤ 原有 inline 缓冲区,直接覆盖。 + * - 若超过阈值,自动切换到 ptr 模式,用户层无需关心。 * - * { - * "name": "RyanJson", - * next ( - * "version": "xxx", - * next ( - * "repository": "https://github.com/Ryan-CW-Code/RyanJson", - * next ( - * "keywords": ["json", "streamlined", "parser"], - * next ( - * "others": { ... } - * ))) - * } - */ - - /* - * 设计特点: - * - 一个 Json 节点最多 malloc 两次(一次节点本身,一次可选的 key/stringValue), - * 对嵌入式系统非常友好,减少 malloc 头部开销, 尽可能的减少内存碎片。 - * - * - key 和 stringValue 必须通过指针管理: - * * 如果直接放在节点里,虽然只需一次 malloc, - * 但修改场景会遇到替换/释放困难。 - * * 用户可能传递的 Json 对象不是指针,无法直接替换节点, - * 要求应用层传递指针会增加侵入性,不符合“应用层无需修改”的目标。 - * - * - 因此采用指针方式,保证灵活性和低侵入性。 + * 链表结构示例: + * { + * "name": "RyanJson", + * next ( + * "version": "xxx", + * next ( + * "repository": "https://github.com/Ryan-CW-Code/RyanJson", + * next ( + * "keywords": [ + * "json", + * next ( + * "streamlined", + * next ( + * "parser" + * )) + * ], + * next ( + * "others": { ... } + * } */ }; @@ -116,23 +165,29 @@ typedef enum RyanJsonTypeString = 4, RyanJsonTypeArray = 5, RyanJsonTypeObject = 6, -} RyanjsonType_e; +} RyanJsonType_e; + +typedef RyanJsonType_e RyanjsonType_e; #define RyanJsonFalse (false) #define RyanJsonTrue (true) -// !兼容之前的类型定义 +/** + * @brief 兼容历史版本类型定义。 + */ typedef bool RyanJsonBool_e; typedef RyanJsonBool_e RyanJsonBool; -// 内存钩子函数 +/** + * @brief 内存钩子函数类型定义。 + */ typedef void *(*RyanJsonMalloc_t)(size_t size); typedef void (*RyanJsonFree_t)(void *block); typedef void *(*RyanJsonRealloc_t)(void *block, size_t size); /** - * !!! 较底层接口, 不推荐用户使用,除非用户知道这些接口意义 - * !!! 一定要看这里,这里的接口不推荐使用 + * @brief 底层访问宏(不建议业务侧直接使用)。 + * @note 仅在明确理解内部内存布局与位字段语义时使用。 */ #define RyanJsonGetMask(bits) (((1U << (bits)) - 1)) #define RyanJsonGetPayloadPtr(pJson) ((uint8_t *)(pJson) + sizeof(struct RyanJsonNode)) @@ -150,56 +205,82 @@ typedef void *(*RyanJsonRealloc_t)(void *block, size_t size); #define RyanJsonGetPayloadNumberIsDoubleByFlag(pJson) RyanJsonGetPayloadFlagField((pJson), 3, RyanJsonGetMask(1)) #define RyanJsonSetPayloadNumberIsDoubleByFlag(pJson, value) RyanJsonSetPayloadFlagField((pJson), 3, RyanJsonGetMask(1), (value)) -#define RyanJsonGetPayloadWhiteKeyByFlag(pJson) RyanJsonGetPayloadFlagField((pJson), 4, RyanJsonGetMask(1)) -#define RyanJsonSetPayloadWhiteKeyByFlag(pJson, value) RyanJsonSetPayloadFlagField((pJson), 4, RyanJsonGetMask(1), (value)) +/** + * @brief key 长度字段编码访问宏。 + * @note 该编码当前受 8bit flag 位宽限制。 + */ +#define RyanJsonGetPayloadEncodeKeyLenByFlag(pJson) RyanJsonGetPayloadFlagField((pJson), 4, RyanJsonGetMask(2)) +#define RyanJsonSetPayloadEncodeKeyLenByFlag(pJson, value) RyanJsonSetPayloadFlagField((pJson), 4, RyanJsonGetMask(2), (value)) + +#define RyanJsonGetPayloadStrIsPtrByFlag(pJson) RyanJsonGetPayloadFlagField((pJson), 6, RyanJsonGetMask(1)) +#define RyanJsonSetPayloadStrIsPtrByFlag(pJson, value) RyanJsonSetPayloadFlagField((pJson), 6, RyanJsonGetMask(1), (value)) + +#define RyanJsonGetPayloadIsLastByFlag(pJson) RyanJsonGetPayloadFlagField((pJson), 7, RyanJsonGetMask(1)) +#define RyanJsonSetPayloadIsLastByFlag(pJson, value) RyanJsonSetPayloadFlagField((pJson), 7, RyanJsonGetMask(1), (value)) -// ! 使用超过8字节后一定要注意 RyanJsonSetPayloadFlagField 目前限制uint8_t类型 -// flag空间不够的时候可以把这个字段弃用,用redis的listpack方法将key和keyLen一起表示,内存占用也挺好,但是复杂度高,有空间就保持现在这样 -#define RyanJsonGetPayloadEncodeKeyLenByFlag(pJson) ((uint8_t)RyanJsonGetPayloadFlagField((pJson), 5, RyanJsonGetMask(2)) + 1) -#define RyanJsonSetPayloadEncodeKeyLenByFlag(pJson, value) RyanJsonSetPayloadFlagField((pJson), 5, RyanJsonGetMask(2), (value)) extern RyanJsonBool_e RyanJsonInsert(RyanJson_t pJson, uint32_t index, RyanJson_t item); -extern void *RyanJsonGetValue(RyanJson_t pJson); -extern RyanJson_t RyanJsonCreateItem(const char *key, RyanJson_t item); // 需用户释放内存 +extern RyanJson_t RyanJsonGetNext(RyanJson_t pJson); + /** - * !!!上面的接口不推荐使用 - * + * @brief 上述底层宏与接口不建议业务侧直接调用。 */ /** - * @brief json对象函数 + * @brief Json 对外接口 */ -extern RyanJsonBool_e RyanJsonInitHooks(RyanJsonMalloc_t _malloc, RyanJsonFree_t _free, RyanJsonRealloc_t _realloc); +extern RyanJsonBool_e RyanJsonInitHooks(RyanJsonMalloc_t userMalloc, RyanJsonFree_t userFree, RyanJsonRealloc_t userRealloc); extern RyanJson_t RyanJsonParseOptions(const char *text, uint32_t size, RyanJsonBool_e requireNullTerminator, - const char **parseEndPtr); // 需用户释放内存 -#define RyanJsonParse(text) RyanJsonParseOptions((text), (uint32_t)RyanJsonStrlen(text), RyanJsonFalse, NULL) // 需用户释放内存 + const char **parseEndPtr); // 需用户释放内存 +extern RyanJson_t RyanJsonParse(const char *text); // 需用户释放内存 + extern void RyanJsonDelete(RyanJson_t pJson); extern void RyanJsonFree(void *block); /** - * @brief 打印json对象函数 + * @brief 打印风格配置 */ -extern char *RyanJsonPrint(RyanJson_t pJson, uint32_t preset, RyanJsonBool_e format, uint32_t *len); // 需用户释放内存 +typedef struct +{ + char *indent; // 缩进字符串 (例如 "\t" 或 " ") + char *newline; // 换行字符串 (例如 "\n" 或 "\r\n") + uint8_t indentLen; // 缩进字符串长度 + uint8_t newlineLen; // 换行字符串长度 + uint8_t spaceAfterColon; // 冒号后空格数量 + RyanJsonBool_e format; // 是否启用格式化逻辑 +} RyanJsonPrintStyle; +extern char *RyanJsonPrintWithStyle(RyanJson_t pJson, uint32_t preset, const RyanJsonPrintStyle *style, uint32_t *len); +extern char *RyanJsonPrintPreallocatedWithStyle(RyanJson_t pJson, char *buffer, uint32_t length, const RyanJsonPrintStyle *style, + uint32_t *len); +extern char *RyanJsonPrint(RyanJson_t pJson, uint32_t preset, RyanJsonBool_e format, uint32_t *len); extern char *RyanJsonPrintPreallocated(RyanJson_t pJson, char *buffer, uint32_t length, RyanJsonBool_e format, uint32_t *len); /** - * @brief json杂项函数 + * @brief Json 杂项函数 */ extern RyanJson_t RyanJsonDuplicate(RyanJson_t pJson); // 需用户释放内存 extern uint32_t RyanJsonMinify(char *text, int32_t textLen); extern RyanJsonBool_e RyanJsonCompare(RyanJson_t leftJson, RyanJson_t rightJson); +extern RyanJsonBool_e RyanJsonCompareOnlyKey(RyanJson_t leftJson, RyanJson_t rightJson); +extern RyanJsonBool_e RyanJsonCompareDouble(double a, double b); extern uint32_t RyanJsonGetSize(RyanJson_t pJson); #define RyanJsonGetArraySize(pJson) RyanJsonGetSize(pJson) /** * @brief 添加相关函数 */ -extern RyanJson_t RyanJsonCreateObject(void); // 如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateNull(const char *key); // 如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateBool(const char *key, RyanJsonBool_e boolean); // 如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateInt(const char *key, int32_t number); // 如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateDouble(const char *key, double number); // 如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateString(const char *key, const char *string); // 如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateArray(void); // 如果没有添加到父json, 则需释放内存 +extern RyanJson_t RyanJsonCreateObject(void); // 如果没有添加到父 Json,则需释放内存 +extern RyanJson_t RyanJsonCreateNull(const char *key); // 如果没有添加到父 Json,则需释放内存 +extern RyanJson_t RyanJsonCreateBool(const char *key, RyanJsonBool_e boolean); // 如果没有添加到父 Json,则需释放内存 +extern RyanJson_t RyanJsonCreateInt(const char *key, int32_t number); // 如果没有添加到父 Json,则需释放内存 +extern RyanJson_t RyanJsonCreateDouble(const char *key, double number); // 如果没有添加到父 Json,则需释放内存 +extern RyanJson_t RyanJsonCreateString(const char *key, const char *string); // 如果没有添加到父 Json,则需释放内存 +extern RyanJson_t RyanJsonCreateArray(void); // 如果没有添加到父 Json,则需释放内存 +/** + * @brief 语法糖 + */ +extern RyanJson_t RyanJsonCreateIntArray(const int32_t *numbers, uint32_t count); +extern RyanJson_t RyanJsonCreateDoubleArray(const double *numbers, uint32_t count); +extern RyanJson_t RyanJsonCreateStringArray(const char **strings, uint32_t count); /** * @brief 分离相关函数 @@ -219,7 +300,9 @@ extern RyanJsonBool_e RyanJsonDeleteByKey(RyanJson_t pJson, const char *key); extern RyanJson_t RyanJsonGetObjectByKey(RyanJson_t pJson, const char *key); extern RyanJson_t RyanJsonGetObjectByIndex(RyanJson_t pJson, uint32_t index); -// 工具宏 +/** + * @brief 工具宏 + */ #define RyanJsonMakeBool(ex) ((ex) ? RyanJsonTrue : RyanJsonFalse) /** @@ -240,29 +323,44 @@ extern RyanJsonBool_e RyanJsonIsArray(RyanJson_t pJson); extern RyanJsonBool_e RyanJsonIsObject(RyanJson_t pJson); extern RyanJsonBool_e RyanJsonIsInt(RyanJson_t pJson); extern RyanJsonBool_e RyanJsonIsDouble(RyanJson_t pJson); +extern RyanJsonBool_e RyanJsonIsDetachedItem(RyanJson_t item); /** - * @brief 取值宏 - * !取值宏使用前一定要RyanJsonIsXXXX类型判断函数做好判断,否则会内存访问越界 + * @brief 节点取值接口。 + * @note 调用前应先判空并使用 `RyanJsonIsXXX` 做类型判断。 */ extern char *RyanJsonGetKey(RyanJson_t pJson); extern char *RyanJsonGetStringValue(RyanJson_t pJson); -#define RyanJsonGetBoolValue(pJson) RyanJsonGetPayloadBoolValueByFlag(pJson) -#define RyanJsonGetIntValue(pJson) (*(int32_t *)RyanJsonGetValue(pJson)) -#define RyanJsonGetDoubleValue(pJson) (*(double *)RyanJsonGetValue(pJson)) -#define RyanJsonGetArrayValue(pJson) (*(RyanJson_t *)RyanJsonGetValue(pJson)) -#define RyanJsonGetObjectValue(pJson) (*(RyanJson_t *)RyanJsonGetValue(pJson)) +extern int32_t RyanJsonGetIntValue(RyanJson_t pJson); +extern double RyanJsonGetDoubleValue(RyanJson_t pJson); +extern RyanJson_t RyanJsonGetObjectValue(RyanJson_t pJson); +extern RyanJson_t RyanJsonGetArrayValue(RyanJson_t pJson); +extern RyanJsonBool_e RyanJsonGetBoolValue(RyanJson_t pJson); /** - * @brief 添加相关函数 - * ! add函数使用前建议RyanJsonIsXXXX宏判断是否是对象 / 数组,否则会内存访问越界 - * ! add函数内部会处理失败情况,如果返回false,不需要用户手动释放内存 + * @brief 变参路径查询底层接口。 + * @note 建议优先使用 `RyanJsonGetObjectToKey/ToIndex` 与 `RyanJsonHasObjectToKey/ToIndex` 宏。 + */ +extern RyanJson_t RyanJsonGetObjectByIndexs(RyanJson_t pJson, uint32_t index, ...); +extern RyanJson_t RyanJsonGetObjectByKeys(RyanJson_t pJson, const char *key, ...); +#define RyanJsonGetObjectToKey(pJson, key, ...) RyanJsonGetObjectByKeys(pJson, (key), ##__VA_ARGS__, NULL) +#define RyanJsonGetObjectToIndex(pJson, index, ...) RyanJsonGetObjectByIndexs(pJson, (index), ##__VA_ARGS__, UINT32_MAX) +#define RyanJsonHasObjectToKey(pJson, key, ...) RyanJsonMakeBool(RyanJsonGetObjectToKey(pJson, key, ##__VA_ARGS__)) +#define RyanJsonHasObjectToIndex(pJson, index, ...) RyanJsonMakeBool(RyanJsonGetObjectToIndex(pJson, index, ##__VA_ARGS__)) + +/** + * @brief Add 系列接口与便捷宏。 + * @note 建议调用前先用 `RyanJsonIsObject/RyanJsonIsArray` 做类型校验。 + * @note Add/Insert 在 `item` 为游离节点时失败会自动释放 `item`。 + * @note `item` 非游离节点时失败不会释放 `item`(保护原树)。 + * @note Object key 必须唯一,重复 key 会失败(Parse 也拒绝重复 key)。 + * @note `AddItem` 仅接受 Array/Object 节点;标量请使用 `AddInt/AddString` 等接口。 */ -#define RyanJsonAddNullToObject(pJson, key) RyanJsonInsert(pJson, UINT32_MAX, RyanJsonCreateNull(key)) -#define RyanJsonAddBoolToObject(pJson, key, boolean) RyanJsonInsert(pJson, UINT32_MAX, RyanJsonCreateBool(key, boolean)) -#define RyanJsonAddIntToObject(pJson, key, number) RyanJsonInsert(pJson, UINT32_MAX, RyanJsonCreateInt(key, number)) -#define RyanJsonAddDoubleToObject(pJson, key, number) RyanJsonInsert(pJson, UINT32_MAX, RyanJsonCreateDouble(key, number)) -#define RyanJsonAddStringToObject(pJson, key, string) RyanJsonInsert(pJson, UINT32_MAX, RyanJsonCreateString(key, string)) +#define RyanJsonAddNullToObject(pJson, key) RyanJsonInsert(pJson, RyanJsonAddPosition, RyanJsonCreateNull(key)) +#define RyanJsonAddBoolToObject(pJson, key, boolean) RyanJsonInsert(pJson, RyanJsonAddPosition, RyanJsonCreateBool(key, boolean)) +#define RyanJsonAddIntToObject(pJson, key, number) RyanJsonInsert(pJson, RyanJsonAddPosition, RyanJsonCreateInt(key, number)) +#define RyanJsonAddDoubleToObject(pJson, key, number) RyanJsonInsert(pJson, RyanJsonAddPosition, RyanJsonCreateDouble(key, number)) +#define RyanJsonAddStringToObject(pJson, key, string) RyanJsonInsert(pJson, RyanJsonAddPosition, RyanJsonCreateString(key, string)) extern RyanJsonBool_e RyanJsonAddItemToObject(RyanJson_t pJson, const char *key, RyanJson_t item); #define RyanJsonAddNullToArray(pJson) RyanJsonAddNullToObject(pJson, NULL) @@ -272,27 +370,29 @@ extern RyanJsonBool_e RyanJsonAddItemToObject(RyanJson_t pJson, const char *key, #define RyanJsonAddStringToArray(pJson, string) RyanJsonAddStringToObject(pJson, NULL, string) #define RyanJsonAddItemToArray(pJson, item) RyanJsonAddItemToObject(pJson, NULL, item) -/** - * @brief 遍历函数 - */ -#define RyanJsonArrayForEach(pJson, item) for ((item) = RyanJsonGetArrayValue(pJson); NULL != (item); (item) = (item)->next) -#define RyanJsonObjectForEach(pJson, item) for ((item) = RyanJsonGetObjectValue(pJson); NULL != (item); (item) = (item)->next) +#define RyanJsonArrayForEach(pJson, item) \ + for ((item) = RyanJsonIsArray(pJson) ? RyanJsonGetArrayValue(pJson) : NULL; NULL != (item); (item) = RyanJsonGetNext(item)) +#define RyanJsonObjectForEach(pJson, item) \ + for ((item) = RyanJsonIsObject(pJson) ? RyanJsonGetObjectValue(pJson) : NULL; NULL != (item); (item) = RyanJsonGetNext(item)) /** - * @brief 修改相关函数 - * !修改函数没有对入参做校验,使用前请做使用RyanJsonIsXXXX类型判断宏做好判断,否则会内存访问越界 + * @brief 同类型值修改接口。 + * @note 修改函数会执行基本参数/类型校验,失败返回 false。 + * @note 仍建议调用前使用 `RyanJsonIsXXX` 做前置判断。 */ extern RyanJsonBool_e RyanJsonChangeKey(RyanJson_t pJson, const char *key); extern RyanJsonBool_e RyanJsonChangeStringValue(RyanJson_t pJson, const char *strValue); -#define RyanJsonChangeBoolValue(pJson, boolean) RyanJsonSetPayloadBoolValueByFlag(pJson, boolean) -#define RyanJsonChangeIntValue(pJson, number) (RyanJsonGetIntValue(pJson) = (number)) -#define RyanJsonChangeDoubleValue(pJson, number) (RyanJsonGetDoubleValue(pJson) = (number)) +extern RyanJsonBool_e RyanJsonChangeIntValue(RyanJson_t pJson, int32_t number); +extern RyanJsonBool_e RyanJsonChangeDoubleValue(RyanJson_t pJson, double number); +extern RyanJsonBool_e RyanJsonChangeBoolValue(RyanJson_t pJson, RyanJsonBool_e boolean); -// 这是change方法的补充,当需要修改value类型时,使用此函数 -// 请参考 changeJsonTest 示例,严格按照规则来使用 /** - * @brief - * !这是change方法的补充,当需要修改value类型时,使用此函数,请参考 RyanJsonBaseTestChangeJson 示例,严格按照规则来使用 + * @brief 节点替换接口(用于修改 value 类型) + * @note 需要跨类型替换时使用 `ReplaceByKey/ReplaceByIndex`。 + * @note 示例:`RyanJsonReplaceByKey(root, "k", RyanJsonCreateObject());` + * @note 示例:`RyanJsonReplaceByIndex(arr, i, RyanJsonCreateString(NULL, "v"));` + * @note Replace 成功后,`item` 所有权转移到目标树。 + * @note Replace 失败后,调用方仍持有 `item`,需自行释放或复用。 */ extern RyanJsonBool_e RyanJsonReplaceByKey(RyanJson_t pJson, const char *key, RyanJson_t item); extern RyanJsonBool_e RyanJsonReplaceByIndex(RyanJson_t pJson, uint32_t index, RyanJson_t item); // object对象也可以使用,但是不推荐 diff --git a/RyanJson/RyanJsonConfig.h b/RyanJson/RyanJsonConfig.h index 4d53a39..39af08f 100644 --- a/RyanJson/RyanJsonConfig.h +++ b/RyanJson/RyanJsonConfig.h @@ -1,5 +1,5 @@ -#ifndef __RyanJsonConfig__ -#define __RyanJsonConfig__ +#ifndef RyanJsonConfig +#define RyanJsonConfig #ifdef __cplusplus extern "C" { @@ -14,6 +14,7 @@ extern "C" { #include #include #include +#include #ifdef RT_VER_NUM #include "rtthread.h" @@ -23,6 +24,8 @@ extern "C" { #define RyanJsonStrcmp rt_strcmp #define RyanJsonSnprintf rt_snprintf #define RyanJsonPlatformAssert(EX) RT_ASSERT(EX) +#define RyanJsonMallocHeaderSize 12U +#define RyanJsonMallocAlign RT_ALIGN_SIZE #else #include #define RyanJsonMemset memset @@ -31,119 +34,154 @@ extern "C" { #define RyanJsonStrcmp strcmp #define RyanJsonSnprintf snprintf #define RyanJsonPlatformAssert(EX) assert(EX) +#define RyanJsonMallocHeaderSize 12U +#define RyanJsonMallocAlign 8U #endif -// 是否启用assert +/** + * @brief RyanJsonEnableAssert: 启用库内断言(RyanJsonAssert / RyanJsonCheckAssert)。 + * @note 默认关闭(注释状态)。 + */ // #define RyanJsonEnableAssert -// 是否支持未对齐访问,未定义时会根据平台选择, -// 一般不用管,如果你明白你的需求就自己定义 -// true 表示支持未对齐访问 -// false 表示不支持未对齐访问 -// UINT8_MAX 标识让RyanJson自动判断,但可能会漏掉支持对齐访问的平台 -#ifndef RyanJsonUnalignedAccessSupported -#define RyanJsonUnalignedAccessSupported UINT8_MAX +/** + * @brief RyanJsonMallocAlign: 内存对齐粒度(字节)。 + * @note 默认值为 8(平台未提前定义时)。 + * @note 必须是 4 的倍数。 + */ +#ifndef RyanJsonMallocAlign +#define RyanJsonMallocAlign 8U #endif -// 限制解析数组/对象中嵌套的深度 -// RyanJson使用递归 序列化/反序列化 json -// 请根据单片机资源合理设置以防止堆栈溢出。 -#ifndef RyanJsonNestingLimit -#define RyanJsonNestingLimit 3000 +/** + * @brief RyanJsonMallocHeaderSize: 分配器头部开销(字节),用于内联阈值估算。 + * @note 默认值为 8(平台未提前定义时)。 + * @note 必须是 4 的倍数。 + */ +#ifndef RyanJsonMallocHeaderSize +#define RyanJsonMallocHeaderSize 8U #endif -// 当 RyanJsonPrint 剩余缓冲空间不足时申请的空间大小 +/** + * @brief RyanJsonPrintfPreAlloSize: RyanJsonPrint 缓冲区不足时,每次扩容的预分配大小。 + * @note 默认值为 64 字节。 + */ #ifndef RyanJsonPrintfPreAlloSize #define RyanJsonPrintfPreAlloSize (64U) #endif /** - * @brief 调试相关配置 - * + * @brief RyanJsonAbsTolerance: 浮点比较的绝对容差。 + * @note 默认值为 1e-8(常见场景足够)。 + * @note 建议范围为 (0.0, 1.0)。 */ -// #define jsonLog(fmt, ...) printf("%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__) -#define jsonLog(...) - -#ifdef RyanJsonEnableAssert -#define RyanJsonAssert(EX) RyanJsonPlatformAssert(EX) -#else -#define RyanJsonAssert(EX) (void)(EX) +#ifndef RyanJsonAbsTolerance +#define RyanJsonAbsTolerance 1e-8 #endif /** - * @brief 判断是否支持未对齐访问 - * + * @brief RyanJsonStrictObjectKeyCheck: 控制 Object 是否允许重复 key。 + * @note true 时 Parse/Insert/ReplaceByIndex 拒绝重复 key。 + * @note false 时允许重复 key,但按 key 的 API 通常只命中第一个节点。 + * @note 默认值为 false。 */ -#if UINT8_MAX == RyanJsonUnalignedAccessSupported -#undef RyanJsonUnalignedAccessSupported - -// Cortex-M0/M0+/M1 属于 ARMv6-M -#if defined(__ARM_ARCH_6M__) -#define RyanJsonUnalignedAccessSupported false - -// Cortex-M3/M4/M7 属于 ARMv7-M/EM -#elif defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) -#define RyanJsonUnalignedAccessSupported true - -// Cortex-M23/M33 属于 ARMv8-M -#elif defined(__ARM_ARCH_8M_BASE__) || defined(__ARM_ARCH_8M_MAIN__) -#define RyanJsonUnalignedAccessSupported true - -// Cortex-A/R 属于 ARMv7-A/R -#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) -#define RyanJsonUnalignedAccessSupported true - -// ARM9/ARM11 等老核 -#elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5E__) -#define RyanJsonUnalignedAccessSupported false +#ifndef RyanJsonStrictObjectKeyCheck +#define RyanJsonStrictObjectKeyCheck false +#endif -// ARMv8-A / ARM64 -#elif defined(__aarch64__) || defined(__ARM_ARCH_8A__) || defined(__ARM_ARCH_9__) -#define RyanJsonUnalignedAccessSupported true +/** + * @brief RyanJsonDefaultAddAtHead: 控制 Add 系列接口(Array/Object)的默认插入方向。 + * @note false 为尾插(保持业务顺序,超大链表时查尾为 O(N))。 + * @note true 为头插(O(1),但遍历/打印时新元素会在前面)。 + * @note 默认值为 false。 + */ +#ifndef RyanJsonDefaultAddAtHead +#define RyanJsonDefaultAddAtHead false +#endif -// RISC-V MCU 默认不支持未对齐访问 -#elif defined(__riscv) -#define RyanJsonUnalignedAccessSupported false +/** + * @brief RyanJsonSnprintfSupportScientific: 声明目标平台 snprintf 是否支持科学计数法(%g/%e)。 + * @note 该配置会影响 double 序列化策略与 RyanJsonDoubleBufferSize 默认值。 + * @note 默认值为 true。 + */ +#ifndef RyanJsonSnprintfSupportScientific +#define RyanJsonSnprintfSupportScientific true +#endif -// x86 / x86-64 -#elif defined(__i386__) || defined(__x86_64__) -#define RyanJsonUnalignedAccessSupported true +/** + * @brief RyanJsonDoubleBufferSize: double 序列化临时缓冲区大小。 + * @note 若支持科学计数法:默认 32(建议 >= 32)。 + * @note 若不支持科学计数法:默认 64(理论上完整输出可到 330+,可按需求增大)。 + */ +#ifndef RyanJsonDoubleBufferSize +#if true == RyanJsonSnprintfSupportScientific +#define RyanJsonDoubleBufferSize 32 +#else +// 不支持科学计数法的平台使用 ".17lf" 最大将会输出 330+ 字节的数据,对于此库来说占用太高了. +// 如果用户可以接受,就修改 RyanJsonDoubleBufferSize 宏,由你来决定RyanJson给 ".17lf" 提供多大缓冲区 +#define RyanJsonDoubleBufferSize 64 +#endif +#endif -// MIPS -#elif defined(__mips__) -#define RyanJsonUnalignedAccessSupported false +/** + * @brief jsonLog: 内部调试日志钩子。 + * @note 默认为空实现。 + * @note 可按需取消下一行注释替换为 printf 等实现。 + */ +// #define jsonLog(fmt, ...) printf("%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__) +#define jsonLog(...) -// PowerPC -#elif defined(__powerpc__) || defined(__ppc__) -#define RyanJsonUnalignedAccessSupported false +#ifdef RyanJsonEnableAssert +#define RyanJsonAssert(EX) RyanJsonPlatformAssert(EX) +#else +#define RyanJsonAssert(EX) +#endif -// SPARC -#elif defined(__sparc__) -#define RyanJsonUnalignedAccessSupported false +/** + * @brief 配置合法性检查(编译期)。 + */ +#if 0 != RyanJsonMallocHeaderSize && RyanJsonMallocHeaderSize % 4 != 0 +#error "RyanJsonMallocHeaderSize 必须是4的倍数" +#endif -// SuperH -#elif defined(__sh__) -#define RyanJsonUnalignedAccessSupported false +#if 0 != RyanJsonMallocAlign && RyanJsonMallocAlign % 4 != 0 +#error "RyanJsonMallocAlign 必须是4的倍数" +#endif -// Alpha -#elif defined(__alpha__) -#define RyanJsonUnalignedAccessSupported true +#if RyanJsonDoubleBufferSize < 8 +#error "RyanJsonDoubleBufferSize 必须大于8" +#endif -// Itanium -#elif defined(__ia64__) -#define RyanJsonUnalignedAccessSupported false +#if true != RyanJsonStrictObjectKeyCheck && false != RyanJsonStrictObjectKeyCheck +#error "RyanJsonStrictObjectKeyCheck 必须是 true 或 false" +#endif -#else -// 默认认为不支持未对齐访问 -#define RyanJsonUnalignedAccessSupported false +#if true != RyanJsonDefaultAddAtHead && false != RyanJsonDefaultAddAtHead +#error "RyanJsonDefaultAddAtHead 必须是 true 或 false" #endif -#endif // UINT8_MAX == RyanJsonUnalignedAccessSupported +/** + * @brief RyanJsonInlineStringSize: key/短字符串内联阈值(单位:字节)。 + * @note 用户可在包含 RyanJson.h 前自行定义。 + * @note C99 预处理器无法对包含 sizeof 的表达式做 #if 校验。 + */ +#ifdef RyanJsonInlineStringSize +#if RyanJsonInlineStringSize <= 1U +#error "RyanJsonInlineStringSize 必须大于1" +#endif +#endif -#if true != RyanJsonUnalignedAccessSupported -#define RyanJsonAlign sizeof(void *) +/** + * @brief RyanJsonAddPosition: Add 系列接口的默认插入索引(用于 RyanJsonInsert)。 + * @note 0 表示头插,UINT32_MAX 表示尾插(按追加语义处理)。 + * @note 默认值由 RyanJsonDefaultAddAtHead 自动推导。 + */ +#ifndef RyanJsonAddPosition +#if true == RyanJsonDefaultAddAtHead +#define RyanJsonAddPosition 0U #else -#define RyanJsonAlign sizeof(uint8_t) +#define RyanJsonAddPosition UINT32_MAX +#endif #endif #ifdef __cplusplus diff --git a/RyanJson/RyanJsonInternal.h b/RyanJson/RyanJsonInternal.h new file mode 100644 index 0000000..225999b --- /dev/null +++ b/RyanJson/RyanJsonInternal.h @@ -0,0 +1,119 @@ +#ifndef RyanJsonInternal_h +#define RyanJsonInternal_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include "RyanJson.h" + +#ifndef RyanJsonInternalApi +#define RyanJsonInternalApi extern +#endif + +#define RyanJsonFlagSize sizeof(uint8_t) +#define RyanJsonKeyFeidLenMaxSize sizeof(uint32_t) +#define RyanJsonAlign(size, align) (((size) + (align) - 1) & ~((align) - 1)) +#define RyanJsonAlignDown(size, align) ((size) & ~((align) - 1)) +#define _checkType(info, type) (RyanJsonGetType(info) == (type)) +#define RyanJsonUnused(x) (void)(x) + +#ifndef RyanJsonInlineStringSize +/** + * @brief RyanJsonInlineStringSize 默认值(单位:字节,与历史版本等价)。 + * @details + * 该宏用于计算“节点内联字符串区可用容量”(不含 flag 字节)。 + * + * 当前实现: + * rawSize = sizeof(void*) - RyanJsonFlagSize + * + sizeof(void*) + * + (RyanJsonMallocHeaderSize / 2) + * + RyanJsonFlagSize + * inlineSize = RyanJsonAlign(rawSize, RyanJsonMallocAlign) - RyanJsonFlagSize + * + * 各项用途: + * - sizeof(void*) - RyanJsonFlagSize: + * flag占用一个字节,32位4字节对齐情况下,减去flag 占用的 1 字节后,剩余部分正好是一个指针槽的净可用载荷。 + * - + sizeof(void*): + * char* 最少占用一个指针大小,所以再加一个指针大小 + * - + (RyanJsonMallocHeaderSize / 2): + * 再加上半个 header 大小的补偿,内存占用和字节内联的一个平衡点 + * - + RyanJsonFlagSize: + * 在对齐前把前面扣掉的 flag 补回,便于统一按总尺寸做 align。 + * - RyanJsonAlign(..., RyanJsonMallocAlign): + * 将总尺寸按 RyanJsonMallocAlign 向上对齐。 + * - - RyanJsonFlagSize: + * 对齐后再扣掉 flag,得到最终“字符串可用字节数”。 + * + * 其中 rawSize 里的 `-RyanJsonFlagSize` 与 `+RyanJsonFlagSize` 会抵消, + * 因此与历史公式完全等价: + * inlineSize = RyanJsonAlign(2 * sizeof(void*) + (RyanJsonMallocHeaderSize / 2), RyanJsonMallocAlign) + * - RyanJsonFlagSize + * + * 32 位示例(sizeof(void*)=4, RyanJsonFlagSize=1, RyanJsonMallocAlign=4): + * - RyanJsonMallocHeaderSize=8: Align(12, 4) - 1 = 11 + * - RyanJsonMallocHeaderSize=12: Align(14, 4) - 1 = 15 + */ +#define RyanJsonInlineStringSize \ + (RyanJsonAlign((sizeof(void *) - RyanJsonFlagSize + sizeof(void *) + (RyanJsonMallocHeaderSize / 2) + RyanJsonFlagSize), \ + RyanJsonMallocAlign) - \ + RyanJsonFlagSize) +// static uint32_t RyanJsonInlineStringSize(void) +// { +// // Step1: 先计算“两个指针槽 + 补偿值”的基础尺寸(暂不做对齐) +// uint32_t baseSize = sizeof(void *) - RyanJsonFlagSize; // 一个指针槽,先扣掉 flag +// baseSize += sizeof(void *) + RyanJsonMallocHeaderSize / 2; // 再加一个指针槽和半个 header 补偿 +// // Step2: 对齐前把 flag 加回,对齐后再减回,得到最终可用字节数 +// return (uint32_t)(RyanJsonAlign(baseSize + RyanJsonFlagSize, RyanJsonMallocAlign) - RyanJsonFlagSize); +// } +#endif + +// 该结构字段语义需与 struct RyanJsonNode 保持一致 +typedef struct +{ + const char *key; + const char *strValue; + + RyanjsonType_e type; + RyanJsonBool_e boolIsTrueFlag; + RyanJsonBool_e numberIsDoubleFlag; +} RyanJsonNodeInfo_t; + +RyanJsonInternalApi RyanJsonMalloc_t jsonMalloc; +RyanJsonInternalApi RyanJsonFree_t jsonFree; +RyanJsonInternalApi RyanJsonRealloc_t jsonRealloc; + +RyanJsonInternalApi uint8_t *RyanJsonInternalGetStrPtrModeBuf(RyanJson_t pJson); +RyanJsonInternalApi void RyanJsonInternalSetStrPtrModeBuf(RyanJson_t pJson, uint8_t *heapPtr); +RyanJsonInternalApi uint8_t *RyanJsonInternalGetStrPtrModeBufAt(RyanJson_t pJson, uint32_t index); +RyanJsonInternalApi uint8_t RyanJsonInternalDecodeKeyLenField(uint8_t encoded); +RyanJsonInternalApi uint8_t RyanJsonInternalCalcLenBytes(uint32_t len); +RyanJsonInternalApi uint32_t RyanJsonInternalGetKeyLen(RyanJson_t pJson); +RyanJsonInternalApi void *RyanJsonInternalGetValue(RyanJson_t pJson); + +RyanJsonInternalApi RyanJson_t RyanJsonInternalNewNode(RyanJsonNodeInfo_t *info); +RyanJsonInternalApi void RyanJsonInternalListInsertAfter(RyanJson_t parent, RyanJson_t prev, RyanJson_t item); +RyanJsonInternalApi RyanJson_t RyanJsonInternalGetParent(RyanJson_t pJson); +RyanJsonInternalApi RyanJsonBool_e RyanJsonInternalChangeString(RyanJson_t pJson, RyanJsonBool_e isNew, const char *key, + const char *strValue); +RyanJsonInternalApi RyanJson_t RyanJsonInternalCreateObjectAndKey(const char *key); +RyanJsonInternalApi RyanJson_t RyanJsonInternalCreateArrayAndKey(const char *key); +// 内部接口:仅用于容器 children 指针改写(调用方需保证 pJson 为 array/object 且非 NULL) +RyanJsonInternalApi RyanJsonBool_e RyanJsonInternalChangeObjectValue(RyanJson_t pJson, RyanJson_t objValue); +RyanJsonInternalApi RyanJsonBool_e RyanJsonInternalStrEq(const char *s1, const char *s2); +RyanJsonInternalApi void *RyanJsonInternalExpandRealloc(void *block, uint32_t oldSize, uint32_t newSize); // Keep if used across modules + +RyanJsonInternalApi RyanJsonBool_e RyanJsonInternalParseDoubleRaw(const uint8_t *currentPtr, uint32_t remainSize, double *numberValuePtr); + +#ifdef RyanJsonLinuxTestEnv +#undef RyanJsonSnprintf +RyanJsonInternalApi RyanJsonBool_e RyanJsonFuzzerShouldFail(uint32_t probability); +RyanJsonInternalApi int32_t RyanJsonSnprintf(char *buf, size_t size, const char *fmt, ...); +RyanJsonInternalApi uint32_t RyanJsonRandRange(uint32_t min, uint32_t max); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/RyanJson/RyanJsonItem.c b/RyanJson/RyanJsonItem.c new file mode 100644 index 0000000..0854ef6 --- /dev/null +++ b/RyanJson/RyanJsonItem.c @@ -0,0 +1,733 @@ +#include "RyanJsonInternal.h" + +/** + * @brief 在对象节点中按 key 查找子节点 + * + * @param pJson 对象节点 + * @param key 目标 key + * @param prevOut 输出前驱节点,可为 NULL + * @return RyanJson_t 命中节点,未命中返回 NULL + */ +static RyanJson_t RyanJsonFindNodeByKey(RyanJson_t pJson, const char *key, RyanJson_t *prevOut) +{ + RyanJsonCheckAssert(NULL != pJson && NULL != key); + RyanJsonCheckReturnNull(_checkType(pJson, RyanJsonTypeObject)); + + RyanJson_t prev = NULL; + RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); + + while (nextItem) + { + // 对象子节点按约定必须带 key,异常场景下直接返回,避免继续访问无效数据 + RyanJsonCheckAssert(RyanJsonIsKey(nextItem)); + if (RyanJsonTrue == RyanJsonInternalStrEq(RyanJsonGetKey(nextItem), key)) + { + if (prevOut) { *prevOut = prev; } + return nextItem; + } + prev = nextItem; + nextItem = RyanJsonGetNext(nextItem); + } + + return NULL; +} + +/** + * @brief 用新节点替换旧节点并维护链关系 + * + * @param prev 旧节点前驱,可为 NULL + * @param oldItem 旧节点 + * @param newItem 新节点 + * @return RyanJsonBool_e 替换是否成功 + */ +static RyanJsonBool_e RyanJsonReplaceNode(RyanJson_t prev, RyanJson_t oldItem, RyanJson_t newItem) +{ + RyanJsonCheckAssert(NULL != oldItem && NULL != newItem); + + // 复制 IsLast 标志位 + if (RyanJsonGetPayloadIsLastByFlag(oldItem)) { RyanJsonSetPayloadIsLastByFlag(newItem, 1); } + else + { + RyanJsonSetPayloadIsLastByFlag(newItem, 0); + } + + // 链接前驱节点 + if (NULL != prev) { prev->next = newItem; } + + // 链接后继节点 + // 即使指向的是父节点(IsLast=1),我们也把指针复制过来,保持线索化结构 + newItem->next = oldItem->next; + + oldItem->next = NULL; + RyanJsonSetPayloadIsLastByFlag(oldItem, 0); + return RyanJsonTrue; +} + +#if true == RyanJsonStrictObjectKeyCheck +/** + * @brief 检查对象中是否存在重复 key(可忽略指定节点) + * + * @param pJson 对象节点 + * @param key 目标 key + * @param skipItem 需跳过的节点 + * @return RyanJsonBool_e 是否存在冲突 + */ +static RyanJsonBool_e RyanJsonObjectHasKeyConflict(RyanJson_t pJson, const char *key, RyanJson_t skipItem) +{ + RyanJson_t item = RyanJsonGetObjectValue(pJson); + while (NULL != item) + { + if (item != skipItem) + { + // 对象节点理论上必须带 key,容错处理避免 release 下异常访问 + RyanJsonCheckAssert(RyanJsonTrue == RyanJsonIsKey(item)); + if (RyanJsonTrue == RyanJsonInternalStrEq(RyanJsonGetKey(item), key)) { return RyanJsonTrue; } + } + item = RyanJsonGetNext(item); + } + + return RyanJsonFalse; +} +#endif + +/** + * @brief 为容器插入场景创建包装节点 + */ +static RyanJson_t RyanJsonCreateItem(const char *key, RyanJson_t item) +{ + RyanJsonCheckAssert(NULL != item); + + RyanjsonType_e type = RyanJsonGetType(item); + RyanJsonNodeInfo_t nodeInfo = { + .type = (RyanJsonTypeArray == type) ? RyanJsonTypeArray : RyanJsonTypeObject, + .key = key, + }; + + RyanJson_t newItem = RyanJsonInternalNewNode(&nodeInfo); + RyanJsonCheckReturnNull(NULL != newItem); + + if (RyanJsonTypeArray == type || RyanJsonTypeObject == type) + { + // 转移子节点所有权 + RyanJson_t children = RyanJsonGetObjectValue(item); + RyanJsonInternalChangeObjectValue(newItem, children); + RyanJsonInternalChangeObjectValue(item, NULL); + + // 更新线索化链表:最后一个子节点的 next 指向新父节点 (newItem) + if (children) + { + RyanJson_t last = children; + while (RyanJsonGetNext(last)) + { + last = RyanJsonGetNext(last); + } + last->next = newItem; + } + + // 销毁旧节点(已剥离子节点) + // 必须切断 next 指针,防止 RyanJsonDelete 继续遍历 + item->next = NULL; + RyanJsonDelete(item); + } + else + { + // 原始类型包装:将 item 作为 newItem 的唯一子节点 + RyanJsonInternalChangeObjectValue(newItem, item); + item->next = newItem; // 最后一个节点指向父节点 + RyanJsonSetPayloadIsLastByFlag(item, 1); + } + + return newItem; +} + +/** + * @brief 基础创建接口(语义直观,统一说明) + * + * 约定: + * - key 可为 NULL,表示无 key 节点(如数组元素) + * - 返回值为新建节点,失败返回 NULL + */ +RyanJson_t RyanJsonCreateNull(const char *key) +{ + RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeNull, .key = key}; + return RyanJsonInternalNewNode(&nodeInfo); +} +RyanJson_t RyanJsonCreateBool(const char *key, RyanJsonBool_e boolean) +{ + RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeBool, .key = key, .boolIsTrueFlag = boolean}; + return RyanJsonInternalNewNode(&nodeInfo); +} +RyanJson_t RyanJsonCreateInt(const char *key, int32_t number) +{ + RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeNumber, .key = key, .numberIsDoubleFlag = RyanJsonFalse}; + + RyanJson_t item = RyanJsonInternalNewNode(&nodeInfo); + RyanJsonCheckReturnNull(NULL != item); + + RyanJsonChangeIntValue(item, number); + return item; +} +RyanJson_t RyanJsonCreateDouble(const char *key, double number) +{ + RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeNumber, .key = key, .numberIsDoubleFlag = RyanJsonTrue}; + RyanJson_t item = RyanJsonInternalNewNode(&nodeInfo); + RyanJsonCheckReturnNull(NULL != item); + + RyanJsonChangeDoubleValue(item, number); + return item; +} +RyanJson_t RyanJsonCreateString(const char *key, const char *string) +{ + RyanJsonCheckReturnNull(NULL != string); + + RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeString, .key = key, .strValue = string}; + return RyanJsonInternalNewNode(&nodeInfo); +} +RyanJsonInternalApi RyanJson_t RyanJsonInternalCreateObjectAndKey(const char *key) +{ + RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeObject, .key = key}; + return RyanJsonInternalNewNode(&nodeInfo); +} +RyanJson_t RyanJsonCreateObject(void) +{ + return RyanJsonInternalCreateObjectAndKey(NULL); +} +RyanJsonInternalApi RyanJson_t RyanJsonInternalCreateArrayAndKey(const char *key) +{ + RyanJsonNodeInfo_t nodeInfo = {.type = RyanJsonTypeArray, .key = key}; + return RyanJsonInternalNewNode(&nodeInfo); +} +RyanJson_t RyanJsonCreateArray(void) +{ + return RyanJsonInternalCreateArrayAndKey(NULL); +} + +/** + * @brief 类型/属性判断接口(语义直观,统一说明) + * + * 约定: + * - 参数为 NULL 时统一返回 RyanJsonFalse + */ +RyanJsonBool_e RyanJsonIsKey(RyanJson_t pJson) +{ + return RyanJsonMakeBool(NULL != pJson && RyanJsonGetPayloadEncodeKeyLenByFlag(pJson)); +} +RyanJsonBool_e RyanJsonIsNull(RyanJson_t pJson) +{ + return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeNull == RyanJsonGetType(pJson)); +} +RyanJsonBool_e RyanJsonIsBool(RyanJson_t pJson) +{ + return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeBool == RyanJsonGetType(pJson)); +} +RyanJsonBool_e RyanJsonIsNumber(RyanJson_t pJson) +{ + return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeNumber == RyanJsonGetType(pJson)); +} +RyanJsonBool_e RyanJsonIsString(RyanJson_t pJson) +{ + return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeString == RyanJsonGetType(pJson)); +} +RyanJsonBool_e RyanJsonIsArray(RyanJson_t pJson) +{ + return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeArray == RyanJsonGetType(pJson)); +} +RyanJsonBool_e RyanJsonIsObject(RyanJson_t pJson) +{ + return RyanJsonMakeBool(NULL != pJson && RyanJsonTypeObject == RyanJsonGetType(pJson)); +} +RyanJsonBool_e RyanJsonIsInt(RyanJson_t pJson) +{ + return RyanJsonMakeBool(RyanJsonIsNumber(pJson) && (RyanJsonFalse == RyanJsonGetPayloadNumberIsDoubleByFlag(pJson))); +} +RyanJsonBool_e RyanJsonIsDouble(RyanJson_t pJson) +{ + return RyanJsonMakeBool(RyanJsonIsNumber(pJson) && (RyanJsonTrue == RyanJsonGetPayloadNumberIsDoubleByFlag(pJson))); +} + +/** + * @brief 检查 item 是否为游离节点(未挂到任何树) + * + * @param item 待检查节点 + * @return RyanJsonBool_e 是否为游离节点 + */ +RyanJsonBool_e RyanJsonIsDetachedItem(RyanJson_t item) +{ + RyanJsonCheckReturnFalse(NULL != item); + RyanJsonCheckReturnFalse(NULL == item->next); + RyanJsonCheckReturnFalse(!RyanJsonGetPayloadIsLastByFlag(item)); + return RyanJsonTrue; +} + +/** + * @brief 获取节点 key 字符串指针 + * + * @param pJson 目标节点 + * @return char* key 字符串,失败返回 NULL + */ +char *RyanJsonGetKey(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + if (RyanJsonFalse == RyanJsonGetPayloadStrIsPtrByFlag(pJson)) + { + uint8_t keyFieldLen = RyanJsonInternalDecodeKeyLenField(RyanJsonGetPayloadEncodeKeyLenByFlag(pJson)); + RyanJsonCheckAssert(keyFieldLen <= RyanJsonKeyFeidLenMaxSize); + return (char *)(RyanJsonGetPayloadPtr(pJson) + RyanJsonFlagSize + keyFieldLen); + } + + return (char *)RyanJsonInternalGetStrPtrModeBufAt(pJson, 0); +} + +/** + * @brief 值读取接口(语义直观,统一说明) + * + * 约定: + * - 调用前需保证 pJson 非 NULL 且类型匹配(先用 RyanJsonIsXXXX 判断) + * - 非匹配类型场景不保证返回值语义 + */ +char *RyanJsonGetStringValue(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + + uint32_t len = 0; + + if (RyanJsonFalse == RyanJsonGetPayloadStrIsPtrByFlag(pJson)) + { + uint8_t keyFieldLen = RyanJsonInternalDecodeKeyLenField(RyanJsonGetPayloadEncodeKeyLenByFlag(pJson)); + RyanJsonCheckAssert(keyFieldLen <= RyanJsonKeyFeidLenMaxSize); + + len += keyFieldLen; + if (RyanJsonIsKey(pJson)) { len += RyanJsonInternalGetKeyLen(pJson) + 1U; } + return (char *)(RyanJsonGetPayloadPtr(pJson) + RyanJsonFlagSize + len); + } + + if (RyanJsonIsKey(pJson)) { len = RyanJsonInternalGetKeyLen(pJson) + 1U; } + + return (char *)RyanJsonInternalGetStrPtrModeBufAt(pJson, len); +} +RyanJsonBool_e RyanJsonGetBoolValue(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + return RyanJsonGetPayloadBoolValueByFlag(pJson); +} +int32_t RyanJsonGetIntValue(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + + int32_t intValue; + RyanJsonMemcpy(&intValue, RyanJsonInternalGetValue(pJson), sizeof(intValue)); + return intValue; +} +double RyanJsonGetDoubleValue(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + + double doubleValue; + RyanJsonMemcpy(&doubleValue, RyanJsonInternalGetValue(pJson), sizeof(doubleValue)); + return doubleValue; +} +RyanJson_t RyanJsonGetObjectValue(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + + RyanJson_t objValue; + RyanJsonMemcpy((void *)&objValue, RyanJsonInternalGetValue(pJson), sizeof(void *)); + return objValue; +} +RyanJson_t RyanJsonGetArrayValue(RyanJson_t pJson) +{ + return RyanJsonGetObjectValue(pJson); +} + +/** + * @brief 公共值修改接口(语义直观,统一说明) + * + * 约定: + * - 公共 Change 接口会做基础参数/类型校验,失败返回 RyanJsonFalse + * - 数值/布尔修改为原位写入 + * - key/string 修改会触发字符串存储布局更新 + */ +RyanJsonBool_e RyanJsonChangeKey(RyanJson_t pJson, const char *key) +{ + RyanJsonCheckReturnFalse(NULL != pJson && NULL != key); + RyanJsonCheckReturnFalse(RyanJsonIsKey(pJson)); + return RyanJsonInternalChangeString(pJson, RyanJsonFalse, key, RyanJsonIsString(pJson) ? RyanJsonGetStringValue(pJson) : NULL); +} + +/** + * @brief 修改字符串节点的 value + * + * @param pJson 字符串节点 + * @param strValue 新 strValue + * @return RyanJsonBool_e 修改是否成功 + */ +RyanJsonBool_e RyanJsonChangeStringValue(RyanJson_t pJson, const char *strValue) +{ + RyanJsonCheckReturnFalse(NULL != pJson && NULL != strValue); + RyanJsonCheckReturnFalse(RyanJsonIsString(pJson)); + return RyanJsonInternalChangeString(pJson, RyanJsonFalse, RyanJsonIsKey(pJson) ? RyanJsonGetKey(pJson) : NULL, strValue); +} +RyanJsonBool_e RyanJsonChangeIntValue(RyanJson_t pJson, int32_t number) +{ + RyanJsonCheckReturnFalse(NULL != pJson); + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonIsInt(pJson)); + RyanJsonMemcpy(RyanJsonInternalGetValue(pJson), &number, sizeof(number)); + return RyanJsonTrue; +} +RyanJsonBool_e RyanJsonChangeDoubleValue(RyanJson_t pJson, double number) +{ + RyanJsonCheckReturnFalse(NULL != pJson); + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonIsDouble(pJson)); + RyanJsonMemcpy(RyanJsonInternalGetValue(pJson), &number, sizeof(number)); + return RyanJsonTrue; +} +RyanJsonBool_e RyanJsonChangeBoolValue(RyanJson_t pJson, RyanJsonBool_e boolean) +{ + RyanJsonCheckReturnFalse(NULL != pJson); + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonIsBool(pJson)); + RyanJsonSetPayloadBoolValueByFlag(pJson, boolean); + return RyanJsonTrue; +} + +/** + * @brief 设置容器节点的首子节点指针(内部接口) + * + * @param pJson 容器节点(Object 或 Array) + * @param objValue 新的首子节点,可为 NULL + * @return RyanJsonBool_e 设置是否成功 + */ +RyanJsonInternalApi RyanJsonBool_e RyanJsonInternalChangeObjectValue(RyanJson_t pJson, RyanJson_t objValue) +{ + RyanJsonCheckAssert(NULL != pJson); + RyanJsonCheckAssert(RyanJsonIsObject(pJson) || RyanJsonIsArray(pJson)); + RyanJsonMemcpy(RyanJsonInternalGetValue(pJson), (void *)&objValue, sizeof(void *)); + return RyanJsonTrue; +} + +/** + * @brief 向对象追加容器节点(array/object) + * + * @param pJson 对象节点 + * @param key 新子节点 key + * @param item 待追加节点(要求游离) + * @return RyanJsonBool_e 添加是否成功 + */ +RyanJsonBool_e RyanJsonAddItemToObject(RyanJson_t pJson, const char *key, RyanJson_t item) +{ + RyanJsonCheckReturnFalse(NULL != item); + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonIsDetachedItem(item)); + + // AddItem 仅支持容器类型(Array/Object),标量请使用 AddInt/AddString 等接口 + RyanjsonType_e type = RyanJsonGetType(item); + if (RyanJsonTypeArray != type && RyanJsonTypeObject != type) + { + RyanJsonDelete(item); + return RyanJsonFalse; + } + + RyanJson_t pItem = RyanJsonCreateItem(key, item); + RyanJsonCheckCode(NULL != pItem, { + RyanJsonDelete(item); + return RyanJsonFalse; + }); + return RyanJsonInsert(pJson, RyanJsonAddPosition, pItem); +} + +/** + * @brief 按索引替换子节点 + * + * @param pJson 父节点(数组或对象) + * @param index 目标索引 + * @param item 新节点(要求游离) + * @return RyanJsonBool_e 替换是否成功 + */ +RyanJsonBool_e RyanJsonReplaceByIndex(RyanJson_t pJson, uint32_t index, RyanJson_t item) +{ + RyanJsonCheckReturnFalse(NULL != pJson && NULL != item); + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonIsDetachedItem(item)); + + RyanJsonCheckReturnFalse(_checkType(pJson, RyanJsonTypeArray) || _checkType(pJson, RyanJsonTypeObject)); + if (_checkType(pJson, RyanJsonTypeObject)) { RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonIsKey(item)); } + + RyanJson_t prev = NULL; + RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); + RyanJsonCheckReturnFalse(NULL != nextItem); + + // 定位目标索引节点 + while (index > 0) + { + prev = nextItem; + nextItem = RyanJsonGetNext(nextItem); + index--; + RyanJsonCheckReturnFalse(NULL != nextItem); + } + + // 严格模式下:Object 不允许替换成重复 key +#if true == RyanJsonStrictObjectKeyCheck + if (_checkType(pJson, RyanJsonTypeObject)) + { + RyanJsonCheckReturnFalse(RyanJsonFalse == RyanJsonObjectHasKeyConflict(pJson, RyanJsonGetKey(item), nextItem)); + } +#endif + + RyanJsonReplaceNode(prev, nextItem, item); + if (NULL == prev) { RyanJsonInternalChangeObjectValue(pJson, item); } + + RyanJsonDelete(nextItem); + return RyanJsonTrue; +} + +/** + * @brief 按 key 替换对象子节点 + * + * @param pJson 对象节点 + * @param key 目标 key + * @param item 新节点(要求游离) + * @return RyanJsonBool_e 替换是否成功 + */ +RyanJsonBool_e RyanJsonReplaceByKey(RyanJson_t pJson, const char *key, RyanJson_t item) +{ + RyanJsonCheckReturnFalse(NULL != pJson && NULL != key && NULL != item); + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonIsDetachedItem(item)); + + RyanJson_t prev = NULL; + RyanJson_t nextItem = RyanJsonFindNodeByKey(pJson, key, &prev); + RyanJsonCheckReturnFalse(NULL != nextItem); + + // 若传入节点没有 key,则构造一个带 key 的包装节点 + if (RyanJsonFalse == RyanJsonIsKey(item)) + { + item = RyanJsonCreateItem(key, item); + RyanJsonCheckReturnFalse(NULL != item); + } + else + { + if (RyanJsonTrue != RyanJsonInternalStrEq(RyanJsonGetKey(item), key)) + { + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonChangeKey(item, key)); + } + } + + RyanJsonReplaceNode(prev, nextItem, item); + if (NULL == prev) { RyanJsonInternalChangeObjectValue(pJson, item); } + + RyanJsonDelete(nextItem); + + return RyanJsonTrue; +} + +/** + * @brief 按索引获取子节点 + * + * @param pJson 父节点(数组或对象) + * @param index 子节点索引 + * @return RyanJson_t 命中节点,失败返回 NULL + */ +RyanJson_t RyanJsonGetObjectByIndex(RyanJson_t pJson, uint32_t index) +{ + RyanJsonCheckReturnNull(NULL != pJson); + + RyanJsonCheckReturnNull(_checkType(pJson, RyanJsonTypeArray) || _checkType(pJson, RyanJsonTypeObject)); + + RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); + RyanJsonCheckReturnNull(NULL != nextItem); + while (index > 0) + { + index--; + nextItem = RyanJsonGetNext(nextItem); + RyanJsonCheckReturnNull(NULL != nextItem); + } + + return nextItem; +} + +/** + * @brief 按 key 获取对象子节点 + * + * @param pJson 对象节点 + * @param key 目标 key + * @return RyanJson_t 命中节点,失败返回 NULL + */ +RyanJson_t RyanJsonGetObjectByKey(RyanJson_t pJson, const char *key) +{ + RyanJsonCheckReturnNull(NULL != pJson && NULL != key); + return RyanJsonFindNodeByKey(pJson, key, NULL); +} + +/** + * @brief 按索引分离子节点(不释放) + * + * @param pJson 父节点(数组或对象) + * @param index 子节点索引 + * @return RyanJson_t 被分离节点,失败返回 NULL + */ +RyanJson_t RyanJsonDetachByIndex(RyanJson_t pJson, uint32_t index) +{ + RyanJsonCheckReturnNull(NULL != pJson); + + RyanJsonCheckReturnNull(_checkType(pJson, RyanJsonTypeArray) || _checkType(pJson, RyanJsonTypeObject)); + + RyanJson_t prev = NULL; + RyanJson_t nextItem = RyanJsonGetObjectValue(pJson); + RyanJsonCheckReturnNull(NULL != nextItem); + + while (index > 0) + { + prev = nextItem; + nextItem = RyanJsonGetNext(nextItem); + index--; + RyanJsonCheckReturnNull(NULL != nextItem); + } + + // 维护线索化链表关系 + RyanJson_t trueNext = RyanJsonGetNext(nextItem); + + if (NULL != prev) + { + prev->next = trueNext; + // trueNext 为 NULL 表示 nextItem 原本是尾节点。 + // 分离后 prev 成为新尾节点,需要回指父节点并设置 IsLast。 + if (NULL == trueNext) + { + RyanJsonSetPayloadIsLastByFlag(prev, 1); + prev->next = pJson; // 指向父节点 + } + } + else + { + RyanJsonInternalChangeObjectValue(pJson, trueNext); + } + + nextItem->next = NULL; + RyanJsonSetPayloadIsLastByFlag(nextItem, 0); + + return nextItem; +} + +/** + * @brief 按 key 分离对象子节点(不释放) + * + * @param pJson 对象节点 + * @param key 目标 key + * @return RyanJson_t 被分离节点,失败返回 NULL + */ +RyanJson_t RyanJsonDetachByKey(RyanJson_t pJson, const char *key) +{ + RyanJsonCheckReturnNull(NULL != pJson && NULL != key); + + RyanJson_t prev = NULL; + RyanJson_t nextItem = RyanJsonFindNodeByKey(pJson, key, &prev); + RyanJsonCheckReturnNull(NULL != nextItem); + + // 维护线索化链表关系 + RyanJson_t trueNext = RyanJsonGetNext(nextItem); + + if (NULL != prev) + { + prev->next = trueNext; + // trueNext 为 NULL 表示 nextItem 原本是尾节点。 + // 分离后 prev 成为新尾节点,需要回指父节点并设置 IsLast。 + if (NULL == trueNext) + { + RyanJsonSetPayloadIsLastByFlag(prev, 1); + prev->next = pJson; // 指向父节点 + } + } + else // 分离的是首节点 + { + RyanJsonInternalChangeObjectValue(pJson, trueNext); + } + + nextItem->next = NULL; + RyanJsonSetPayloadIsLastByFlag(nextItem, 0); + + return nextItem; +} + +/** + * @brief 按索引删除子节点 + * + * @param pJson 父节点(数组或对象) + * @param index 子节点索引 + * @return RyanJsonBool_e 删除是否成功 + */ +RyanJsonBool_e RyanJsonDeleteByIndex(RyanJson_t pJson, uint32_t index) +{ + RyanJsonCheckReturnFalse(NULL != pJson); + + RyanJson_t nextItem = RyanJsonDetachByIndex(pJson, index); + RyanJsonCheckReturnFalse(NULL != nextItem); + + RyanJsonDelete(nextItem); + return RyanJsonTrue; +} + +/** + * @brief 按 key 删除对象子节点 + * + * @param pJson 对象节点 + * @param key 目标 key + * @return RyanJsonBool_e 删除是否成功 + */ +RyanJsonBool_e RyanJsonDeleteByKey(RyanJson_t pJson, const char *key) +{ + RyanJsonCheckReturnFalse(NULL != pJson && NULL != key); + + RyanJson_t nextItem = RyanJsonDetachByKey(pJson, key); + RyanJsonCheckReturnFalse(NULL != nextItem); + + RyanJsonDelete(nextItem); + return RyanJsonTrue; +} + +/** + * @brief 向容器按索引插入子节点 + * + * @param pJson 父节点(数组或对象) + * @param index 插入位置,超范围等价尾插 + * @param item 待插入节点(要求游离) + * @return RyanJsonBool_e 插入是否成功 + */ +RyanJsonBool_e RyanJsonInsert(RyanJson_t pJson, uint32_t index, RyanJson_t item) +{ + RyanJson_t nextItem = NULL; + RyanJson_t prev = NULL; + + RyanJsonCheckReturnFalse(NULL != item); + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonIsDetachedItem(item)); + RyanJsonCheckCode(NULL != pJson, { goto error__; }); + + RyanJsonCheckCode(_checkType(pJson, RyanJsonTypeArray) || (_checkType(pJson, RyanJsonTypeObject) && RyanJsonIsKey(item)), { + jsonLog("error__ 不是正确类型 %d\r\n", index); + goto error__; + }); + + // 严格模式下:Object 从插入入口拒绝重复 key +#if true == RyanJsonStrictObjectKeyCheck + if (_checkType(pJson, RyanJsonTypeObject)) + { + RyanJsonCheckCode(RyanJsonFalse == RyanJsonObjectHasKeyConflict(pJson, RyanJsonGetKey(item), NULL), { goto error__; }); + } +#endif + + nextItem = RyanJsonGetObjectValue(pJson); + while (nextItem && index > 0) + { + prev = nextItem; + nextItem = RyanJsonGetNext(nextItem); + index--; + } + + if (NULL != prev) { RyanJsonInternalListInsertAfter(pJson, prev, item); } + else + { + // prev 为 NULL 表示头插,交给统一链表插入函数处理 + RyanJsonInternalListInsertAfter(pJson, NULL, item); + } + + return RyanJsonTrue; + +error__: + RyanJsonDelete(item); + return RyanJsonFalse; +} diff --git a/RyanJson/RyanJsonParse.c b/RyanJson/RyanJsonParse.c new file mode 100644 index 0000000..5e32b2b --- /dev/null +++ b/RyanJson/RyanJsonParse.c @@ -0,0 +1,791 @@ +#include "RyanJsonInternal.h" + +typedef struct +{ + const uint8_t *currentPtr; // 待解析字符串地址 + uint32_t remainSize; // 待解析字符串剩余长度 +} RyanJsonParseBuffer; + +// 相关宏定义已迁移到 RyanJsonInternal.h +#define parseBufAdvanceCurrentPrt(parseBuf, bytesToAdvance) \ + do \ + { \ + (parseBuf)->currentPtr += (bytesToAdvance); \ + (parseBuf)->remainSize -= (bytesToAdvance); \ + } while (0) + +// 是否还有可读的待解析文本在指定索引处 +#define parseBufHasRemainAtIndex(parseBuf, index) ((index) < (parseBuf)->remainSize) +// 是否还有可读的待解析文本 +#define parseBufHasRemainBytes(parseBuf, bytes) ((parseBuf)->remainSize >= (bytes)) +#define parseBufHasRemain(parseBuf) parseBufHasRemainBytes(parseBuf, 1) + +/** + * @brief 尝试向前移动解析缓冲区指针 + */ +static inline RyanJsonBool_e RyanJsonParseBufTryAdvanceCurrentPtr(RyanJsonParseBuffer *parseBuf, uint32_t bytesToAdvance) +{ + RyanJsonCheckAssert(NULL != parseBuf); + +#ifdef isEnableFuzzer + if (RyanJsonFuzzerShouldFail(800)) { bytesToAdvance = UINT32_MAX; } +#endif + + if (parseBufHasRemainBytes(parseBuf, bytesToAdvance)) + { + parseBufAdvanceCurrentPrt(parseBuf, bytesToAdvance); + return RyanJsonTrue; + } + + return RyanJsonFalse; +} + +/** + * @brief 跳过无意义的字符 + */ +static inline RyanJsonBool_e RyanJsonParseBufSkipWhitespace(RyanJsonParseBuffer *parseBuf) +{ + RyanJsonCheckAssert(NULL != parseBuf); + +#ifdef isEnableFuzzer + RyanJsonCheckReturnFalse(!RyanJsonFuzzerShouldFail(1500)); +#endif + + while (parseBufHasRemain(parseBuf)) + { + uint8_t cursor = *parseBuf->currentPtr; + if (' ' == cursor || '\n' == cursor || '\r' == cursor || '\t' == cursor) + { + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + } + else + { + break; + } + } + + return RyanJsonTrue; +} + +/** + * @brief 解析 4 位十六进制文本(XXXX) + * + * @param text 十六进制文本起始地址 + * @param value 解析结果输出 + * @return RyanJsonBool_e 解析是否成功 + */ +static RyanJsonBool_e RyanJsonParseHex(const uint8_t *text, uint32_t *value) +{ + RyanJsonCheckAssert(NULL != text && NULL != value); + uint32_t valueTemp = 0; + + for (uint8_t i = 0; i < 4; ++i) + { + switch (text[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': valueTemp = (valueTemp << 4) + (text[i] - '0'); break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': valueTemp = (valueTemp << 4) + 10 + (text[i] - 'a'); break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': valueTemp = (valueTemp << 4) + 10 + (text[i] - 'A'); break; + default: return RyanJsonFalse; + } + } + + *value = valueTemp; + + return RyanJsonTrue; +} + +/** + * @brief 解析 Json number(含符号/小数/指数) + * + * @param parseBuf 解析缓冲区 + * @param numberValuePtr 输出数值 + * @param isIntPtr 输出是否为整数 + * @return RyanJsonBool_e 解析是否成功 + */ +static RyanJsonBool_e RyanJsonInternalParseDouble(RyanJsonParseBuffer *parseBuf, double *numberValuePtr, RyanJsonBool_e *isIntPtr) +{ + RyanJsonCheckAssert(NULL != parseBuf && NULL != numberValuePtr && NULL != isIntPtr); + + double number = 0; + int32_t scale = 0; + int32_t e_sign = 1; + int32_t e_scale = 0; + RyanJsonBool_e isNegative = RyanJsonFalse; + RyanJsonBool_e isInt = RyanJsonTrue; + + // 处理符号 + if ('-' == *parseBuf->currentPtr) + { + isNegative = RyanJsonTrue; + // 这个不会失败因为进来前已经判断过 parseBufHasRemain(parseBuf) + RyanJsonAssertAlwaysEval(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9'); + } + + // 前导0是非法的 + if ('0' == *parseBuf->currentPtr) + { + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + // 前导0后面不允许跟数字,比如"0123" + if (parseBufHasRemain(parseBuf)) { RyanJsonCheckReturnFalse(*parseBuf->currentPtr < '0' || *parseBuf->currentPtr > '9'); } + } + + // 整数部分 + while (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9') + { + number = number * 10.0 + (*parseBuf->currentPtr - '0'); + // 数值过大导致溢出 + RyanJsonCheckReturnFalse(isfinite(number)); + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + } + + // 小数部分 + if (parseBufHasRemain(parseBuf) && '.' == *parseBuf->currentPtr) + { + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9'); + + while (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9') + { + number = number * 10.0 + (*parseBuf->currentPtr - '0'); + RyanJsonCheckReturnFalse(isfinite(number)); + scale--; // 每读一位小数,scale减一 + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + } + isInt = RyanJsonFalse; + } + + // 指数部分 + if (parseBufHasRemain(parseBuf) && ('e' == *parseBuf->currentPtr || 'E' == *parseBuf->currentPtr)) + { + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf)); + + // 只有遇到 +/- 符号时才跳过 + if ('+' == *parseBuf->currentPtr || '-' == *parseBuf->currentPtr) + { + e_sign = ('-' == *parseBuf->currentPtr) ? -1 : 1; + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + } + + RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9'); + + while (parseBufHasRemain(parseBuf) && *parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9') + { + int32_t digit = (int32_t)(*parseBuf->currentPtr - '0'); + // 防止指数累乘出现有符号溢出 + RyanJsonCheckReturnFalse(e_scale <= (INT32_MAX / 10)); + RyanJsonCheckReturnFalse(e_scale < (INT32_MAX / 10) || digit <= (INT32_MAX % 10)); + e_scale = e_scale * 10 + digit; + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + } + isInt = RyanJsonFalse; + } + + // 判断符号 + if (RyanJsonTrue == isNegative) { number = -number; } + + // 浮点数还需要处理 + if (RyanJsonFalse == isInt) + { + // 使用更宽位数拼接指数,避免中间表达式溢出 + int64_t exponent = (int64_t)scale + (int64_t)e_sign * (int64_t)e_scale; + double expFactor = pow(10.0, (double)exponent); + number *= expFactor; + RyanJsonCheckReturnFalse(isfinite(number)); + } + + *numberValuePtr = number; + *isIntPtr = isInt; + return RyanJsonTrue; +} + +/** + * @brief 解析文本中的数字并创建 Json 节点 + */ +static RyanJsonBool_e RyanJsonParseNumber(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out) +{ + RyanJsonCheckAssert(NULL != parseBuf && NULL != out); + + double number = 0; + RyanJsonBool_e isInt = RyanJsonTrue; + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonInternalParseDouble(parseBuf, &number, &isInt)); + + // 创建 Json 节点 + RyanJson_t newItem = NULL; + if (RyanJsonTrue == isInt && number >= INT32_MIN && number <= INT32_MAX) { newItem = RyanJsonCreateInt(key, (int32_t)number); } + else + { + newItem = RyanJsonCreateDouble(key, number); + } + + RyanJsonCheckReturnFalse(NULL != newItem); + + *out = newItem; + return RyanJsonTrue; +} + +/** + * @brief 预扫描字符串长度并统计是否包含转义字符 + * + * @param parseBuf 解析缓冲区(当前指向起始双引号) + * @param lenPtr 输出解码后的字节长度(不含 '\0') + * @param hasEscapePtr 输出是否包含转义字符 + * @return RyanJsonBool_e 扫描是否成功 + */ +static RyanJsonBool_e RyanJsonParseStringBufferGetLen(RyanJsonParseBuffer *parseBuf, uint32_t *lenPtr, RyanJsonBool_e *hasEscapePtr) +{ + RyanJsonCheckAssert(NULL != parseBuf && NULL != lenPtr && NULL != hasEscapePtr); + + uint32_t len = 0; + RyanJsonBool_e hasEscape = RyanJsonFalse; + + // 不是字符串 + RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf) && '\"' == *parseBuf->currentPtr); + + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1)); + + // 获取字符串解码后的实际字节长度 + for (uint32_t i = 0;;) + { + RyanJsonCheckReturnFalse(parseBufHasRemainAtIndex(parseBuf, i)); + + uint8_t ch = parseBuf->currentPtr[i]; + + if ('\"' == ch) { break; } + + // 检查非法控制字符 (ASCII 0–31) + RyanJsonCheckReturnFalse(ch > 0x1F); + + if ('\\' != ch) + { + len++; + i++; + continue; + } + + // 转义字符 + hasEscape = RyanJsonTrue; + RyanJsonCheckReturnFalse(parseBufHasRemainAtIndex(parseBuf, i + 1)); + uint8_t esc = parseBuf->currentPtr[i + 1]; + switch (esc) + { + case '\"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + len++; + i += 2; + break; + case 'u': { + RyanJsonCheckReturnFalse(parseBufHasRemainAtIndex(parseBuf, i + 5)); + // 十六进制合法性在后续真正解码时校验 + + // 简化:每个 \\uXXXX 预估最大 4 字节 UTF-8 + len += 4; + i += 6; + break; + } + default: +#ifdef isEnableFuzzer + if (RyanJsonFuzzerShouldFail(2)) + { + len++; + i += 2; + break; + } +#endif + return RyanJsonFalse; + } + } + + *lenPtr = len; + *hasEscapePtr = hasEscape; + return RyanJsonTrue; +} + +/** + * @brief 将 Json 字符串片段解码到目标缓冲区 + * + * @note 调用前必须先执行 RyanJsonParseStringBufferGetLen 获取长度与转义信息。 + */ +static RyanJsonBool_e RyanJsonParseStringBuffer(RyanJsonParseBuffer *parseBuf, char *buffer, uint32_t len, RyanJsonBool_e hasEscape) +{ + uint8_t *outCurrentPtr = (uint8_t *)buffer; + + // 获取长度时已确保有结尾引号 + if (RyanJsonFalse == hasEscape) + { + RyanJsonMemcpy(outCurrentPtr, parseBuf->currentPtr, len); + outCurrentPtr[len] = '\0'; + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, len), { goto error__; }); + + // 跳过结尾引号 + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1), { goto error__; }); + return RyanJsonTrue; + } + + // 预扫描长度阶段已确保字符串一定存在结束引号 + while ('\"' != *parseBuf->currentPtr) + { + // 普通字符 + if ('\\' != *parseBuf->currentPtr) + { + *outCurrentPtr++ = *parseBuf->currentPtr; + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1), { goto error__; }); + continue; + } + + // 转义字符 + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1), { goto error__; }); + switch (*parseBuf->currentPtr) + { + + case 'b': *outCurrentPtr++ = '\b'; break; + case 'f': *outCurrentPtr++ = '\f'; break; + case 'n': *outCurrentPtr++ = '\n'; break; + case 'r': *outCurrentPtr++ = '\r'; break; + case 't': *outCurrentPtr++ = '\t'; break; + case '\"': + case '\\': + case '/': *outCurrentPtr++ = *parseBuf->currentPtr; break; + + case 'u': { + // 获取 Unicode 字符 + uint64_t codepoint = 0; + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 4), { goto error__; }); + uint32_t firstCode = 0; + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseHex(parseBuf->currentPtr - 3, &firstCode), { goto error__; }); + // 检查是否有效 + RyanJsonCheckCode(firstCode < 0xDC00 || firstCode > 0xDFFF, { goto error__; }); + + if (firstCode >= 0xD800 && firstCode <= 0xDBFF) // UTF16 代理对 + { + RyanJsonCheckCode(parseBufHasRemainAtIndex(parseBuf, 2), { goto error__; }); + + RyanJsonCheckCode('\\' == parseBuf->currentPtr[1] && 'u' == parseBuf->currentPtr[2], { + goto error__; // 缺少代理的后半部分 + }); + + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 6), { goto error__; }); + uint32_t secondCode = 0; + // 读取代理后半部分 + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseHex(parseBuf->currentPtr - 3, &secondCode), + { goto error__; }); + RyanJsonCheckCode(secondCode >= 0xDC00 && secondCode <= 0xDFFF, { + goto error__; // 无效的代理后半部分 + }); + + codepoint = 0x10000 + (((firstCode & 0x3FF) << 10) | (secondCode & 0x3FF)); + } + else + { + codepoint = firstCode; + } + + // 将 Unicode 码点编码为 UTF-8 + uint8_t utf8Length; + uint8_t firstByteMark; + if (codepoint < 0x80) + { + utf8Length = 1; // ASCII:0xxxxxxx + firstByteMark = 0; + } + else if (codepoint < 0x800) + { + utf8Length = 2; // 双字节:110xxxxx 10xxxxxx + firstByteMark = 0xC0; // 11000000 + } + else if (codepoint < 0x10000) + { + utf8Length = 3; // 三字节:1110xxxx 10xxxxxx 10xxxxxx + firstByteMark = 0xE0; // 11100000 + } + else + { + utf8Length = 4; // 四字节:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + firstByteMark = 0xF0; // 11110000 + } + + // 先从末尾写 continuation byte(10xxxxxx) + for (uint8_t utf8Position = (uint8_t)(utf8Length - 1); utf8Position > 0; utf8Position--) + { + outCurrentPtr[utf8Position] = (uint8_t)((codepoint | 0x80) & 0xBF); // 10xxxxxx + codepoint >>= 6; + } + + // 再写首字节 + if (utf8Length > 1) { outCurrentPtr[0] = (uint8_t)((codepoint | firstByteMark) & 0xFF); } + else + { + outCurrentPtr[0] = (uint8_t)(codepoint & 0x7F); + } + outCurrentPtr += utf8Length; + break; + } + + default: goto error__; + } + + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1), { goto error__; }); + } + *outCurrentPtr = '\0'; + + RyanJsonCheckAssert('\"' == *parseBuf->currentPtr); + + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufTryAdvanceCurrentPtr(parseBuf, 1), { goto error__; }); + return RyanJsonTrue; + +error__: + return RyanJsonFalse; +} + +/** + * @brief 解析字符串节点并创建 Json string 节点 + */ +static RyanJsonBool_e RyanJsonParseString(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out) +{ + RyanJsonCheckAssert(NULL != parseBuf && NULL != out); + + uint32_t len; + RyanJsonBool_e hasEscape = RyanJsonFalse; + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseStringBufferGetLen(parseBuf, &len, &hasEscape)); + + if (len + 1 > RyanJsonInlineStringSize) + { + char *bufferMalloc = (char *)jsonMalloc((size_t)(len + 1U)); + RyanJsonCheckReturnFalse(NULL != bufferMalloc); + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseStringBuffer(parseBuf, bufferMalloc, len, hasEscape), { + jsonFree(bufferMalloc); + return RyanJsonFalse; + }); + + RyanJson_t newItem = RyanJsonCreateString(key, bufferMalloc); + RyanJsonCheckCode(NULL != newItem, { + jsonFree(bufferMalloc); + return RyanJsonFalse; + }); + + jsonFree(bufferMalloc); + *out = newItem; + } + else + { + char buffer[RyanJsonInlineStringSize] = {0}; + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseStringBuffer(parseBuf, buffer, len, hasEscape)); + RyanJson_t newItem = RyanJsonCreateString(key, buffer); + RyanJsonCheckReturnFalse(NULL != newItem); + *out = newItem; + } + + return RyanJsonTrue; +} + +/** + * @brief 解析单个 Json 值(非递归,仅创建当前层节点) + */ +static RyanJsonBool_e RyanJsonParseValue(RyanJsonParseBuffer *parseBuf, char *key, RyanJson_t *out) +{ + RyanJsonCheckAssert(NULL != parseBuf && NULL != out); + + RyanJsonCheckReturnFalse(parseBufHasRemain(parseBuf)); + + *out = NULL; + + if ('\"' == *parseBuf->currentPtr) { return RyanJsonParseString(parseBuf, key, out); } + if ('{' == *parseBuf->currentPtr) + { + *out = RyanJsonInternalCreateObjectAndKey(key); + RyanJsonCheckReturnFalse(NULL != *out); + parseBufAdvanceCurrentPrt(parseBuf, 1); // 消费掉 '{',后续迭代解析器会处理内部 + return RyanJsonTrue; + } + if ('-' == *parseBuf->currentPtr || (*parseBuf->currentPtr >= '0' && *parseBuf->currentPtr <= '9')) + { + return RyanJsonParseNumber(parseBuf, key, out); + } + if ('[' == *parseBuf->currentPtr) + { + *out = RyanJsonInternalCreateArrayAndKey(key); + RyanJsonCheckReturnFalse(NULL != *out); + parseBufAdvanceCurrentPrt(parseBuf, 1); // 消费掉 '[',后续迭代解析器会处理内部 + return RyanJsonTrue; + } + + if (parseBufHasRemainBytes(parseBuf, 4) && 0 == strncmp((const char *)parseBuf->currentPtr, "null", 4)) + { + *out = RyanJsonCreateNull(key); + RyanJsonCheckReturnFalse(NULL != *out); + + parseBufAdvanceCurrentPrt(parseBuf, 4); + return RyanJsonTrue; + } + if (parseBufHasRemainBytes(parseBuf, 5) && 0 == strncmp((const char *)parseBuf->currentPtr, "false", 5)) + { + *out = RyanJsonCreateBool(key, RyanJsonFalse); + RyanJsonCheckReturnFalse(NULL != *out); + + parseBufAdvanceCurrentPrt(parseBuf, 5); + return RyanJsonTrue; + } + if (parseBufHasRemainBytes(parseBuf, 4) && 0 == strncmp((const char *)parseBuf->currentPtr, "true", 4)) + { + *out = RyanJsonCreateBool(key, RyanJsonTrue); + RyanJsonCheckReturnFalse(NULL != *out); + + parseBufAdvanceCurrentPrt(parseBuf, 4); + return RyanJsonTrue; + } + + return RyanJsonFalse; +} + +/** + * @brief 迭代解析器 (使用线索链表维护父子关系,不使用显式栈) + */ +static RyanJsonBool_e RyanJsonParseIterative(RyanJsonParseBuffer *parseBuf, RyanJson_t *root) +{ + RyanJsonCheckAssert(NULL != parseBuf && NULL != root); + + // 先解析根节点 + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonParseValue(parseBuf, NULL, root)); + + // 如果是标量 (String, Number, Bool, Null),直接返回,无需迭代 + if (!RyanJsonIsArray(*root) && !RyanJsonIsObject(*root)) { return RyanJsonTrue; } + + // 初始化迭代状态 + RyanJson_t scopeParent = *root; // 当前容器 (父节点) + RyanJson_t lastSibling = NULL; // 同级上一个节点 (用来链接 sibling->next) + + char shortKey[RyanJsonInlineStringSize]; // 栈上短 key 缓存 + char *key = NULL; // 指向当前使用的 key (shortKey 或 堆内存) + RyanJsonBool_e isKeyAllocated = RyanJsonFalse; // 标记 key 是否需要释放 + RyanJson_t newItem = NULL; // 新解析出的节点 + + while (1) + { + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufSkipWhitespace(parseBuf), { goto error__; }); + RyanJsonCheckCode(parseBufHasRemain(parseBuf), { goto error__; }); + + // 阶段:检查当前容器是否结束(']' 或 '}') + uint8_t ch = *parseBuf->currentPtr; + RyanJsonBool_e scopeParentIsArray = RyanJsonIsArray(scopeParent); + + if ((scopeParentIsArray && ']' == ch) || (!scopeParentIsArray && '}' == ch)) + { + parseBufAdvanceCurrentPrt(parseBuf, 1); + + // 当前容器已经闭合,接下来回溯到父容器。 + // 父容器指针保存在 scopeParent->next(下沉时写入的线索)。 + + // 如果回到根节点,说明整个 Json 解析完成 + if (scopeParent == *root) { return RyanJsonTrue; } + + // 读取当前容器的父容器 + RyanJson_t parent = scopeParent->next; + + // 更新回溯后的层级状态 + // 回到父层后,当前容器变成上一层的 lastSibling。 + lastSibling = scopeParent; + scopeParent = parent; + + // 继续检查父层后续(逗号或结束符) + continue; + } + + // 阶段:处理同层分隔符 + if (lastSibling) + { + if (',' == ch) + { + parseBufAdvanceCurrentPrt(parseBuf, 1); + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufSkipWhitespace(parseBuf), { goto error__; }); + } + else + { + goto error__; // 缺少逗号 + } + } + + // 阶段:解析对象 key(仅对象) + if (!scopeParentIsArray) + { + uint32_t len; + RyanJsonBool_e hasEscape = RyanJsonFalse; + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseStringBufferGetLen(parseBuf, &len, &hasEscape), { goto error__; }); + + // 短 key 优化:优先使用栈内存 + if (len + 1 > RyanJsonInlineStringSize) + { + key = (char *)jsonMalloc((size_t)(len + 1U)); + RyanJsonCheckCode(NULL != key, { goto error__; }); + isKeyAllocated = RyanJsonTrue; + } + else + { + key = shortKey; + isKeyAllocated = RyanJsonFalse; + } + + // 解析 key 到缓冲区(key 指向堆内存或栈内存) + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseStringBuffer(parseBuf, key, len, hasEscape), { goto error__; }); + + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufSkipWhitespace(parseBuf), { goto error__; }); + RyanJsonCheckCode(parseBufHasRemain(parseBuf) && ':' == *parseBuf->currentPtr, { goto error__; }); + parseBufAdvanceCurrentPrt(parseBuf, 1); + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseBufSkipWhitespace(parseBuf), { goto error__; }); + + // 严格模式下:对象从源头拒绝重复 key,避免后续语义歧义(Get/Replace/Compare) +#if true == RyanJsonStrictObjectKeyCheck + RyanJsonCheckCode(RyanJsonFalse == RyanJsonHasObjectByKey(scopeParent, key), { goto error__; }); +#endif + } + else + { + key = NULL; // 数组没有 key + isKeyAllocated = RyanJsonFalse; + } + + // 阶段:解析 value + // 解析值 (可能是标量,也可能是新的容器) + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseValue(parseBuf, key, &newItem), { goto error__; }); + + // 释放动态分配的 key(Node 内部已拷贝一份) + if (isKeyAllocated) + { + jsonFree(key); + isKeyAllocated = RyanJsonFalse; + } + key = NULL; // 重置 key 指针 + + // 阶段:挂接到父容器 + RyanJsonInternalListInsertAfter(scopeParent, lastSibling, newItem); + + lastSibling = newItem; // 更新游标 + + // 阶段:遇到容器时下沉 + if (_checkType(newItem, RyanJsonTypeArray) || _checkType(newItem, RyanJsonTypeObject)) + { + // 更新下沉后的层级状态 + scopeParent = newItem; + lastSibling = NULL; // 新容器初始没有子节点 + } + } + +error__: + // 失败收敛路径:释放临时资源并清理已构建的树 + if (isKeyAllocated) { jsonFree(key); } + + // 删除根节点(因为已经链接好了,删除根节点会递归删除所有已解析的部分) + RyanJsonDelete(*root); + *root = NULL; + + return RyanJsonFalse; +} + +/** + * @brief 校验解析结束位置是否合法 + * + * @param parseBuf 解析缓冲区 + * @param requireNullTerminator RyanJsonTrue 时要求仅剩空白 + * @return RyanJsonBool_e 校验是否成功 + */ +static RyanJsonBool_e RyanJsonParseCheckNullTerminator(RyanJsonParseBuffer *parseBuf, RyanJsonBool_e requireNullTerminator) +{ + RyanJsonCheckAssert(NULL != parseBuf); + + if (requireNullTerminator) + { + // 故意不检查,允许空白 + (void)RyanJsonParseBufSkipWhitespace(parseBuf); + + // 上面已经去掉空白,如果后面还有数据,则失败 + RyanJsonCheckReturnFalse(!parseBufHasRemain(parseBuf)); + } + + return RyanJsonTrue; +} + +/** + * @brief 解析 Json 文本(可配置长度与尾部校验) + * + * @param text 输入文本 + * @param size 文本长度 + * @param requireNullTerminator 是否要求解析后仅剩空白 + * @param parseEndPtr 输出解析结束位置,可为 NULL + * @return RyanJson_t 解析成功返回根节点,失败返回 NULL + */ +RyanJson_t RyanJsonParseOptions(const char *text, uint32_t size, RyanJsonBool_e requireNullTerminator, const char **parseEndPtr) +{ + RyanJsonCheckReturnNull(NULL != text); + + RyanJsonParseBuffer parseBuf = {.currentPtr = (const uint8_t *)text, .remainSize = size}; + RyanJsonCheckReturnNull(RyanJsonTrue == RyanJsonParseBufSkipWhitespace(&parseBuf)); + + RyanJson_t pJson; + RyanJsonCheckReturnNull(RyanJsonTrue == RyanJsonParseIterative(&parseBuf, &pJson)); + + // 检查解析后的文本后面是否有无意义的字符 + RyanJsonCheckCode(RyanJsonTrue == RyanJsonParseCheckNullTerminator(&parseBuf, requireNullTerminator), { + RyanJsonDelete(pJson); + return NULL; + }); + + if (parseEndPtr) { *parseEndPtr = (const char *)parseBuf.currentPtr; } + + return pJson; +} + +/** + * @brief 解析以 '\\0' 结尾的 Json 文本 + * + * @param text 输入文本 + * @return RyanJson_t 解析成功返回根节点,失败返回 NULL + */ +RyanJson_t RyanJsonParse(const char *text) +{ + RyanJsonCheckReturnNull(NULL != text); + return RyanJsonParseOptions(text, (uint32_t)RyanJsonStrlen(text), RyanJsonFalse, NULL); +} + +/** + * @brief 解析原始 number 文本(打印回读校验辅助) + * + * @param currentPtr number 文本起始地址 + * @param remainSize number 文本长度 + * @param numberValuePtr 输出数值 + * @return RyanJsonBool_e 解析是否成功 + */ +RyanJsonInternalApi RyanJsonBool_e RyanJsonInternalParseDoubleRaw(const uint8_t *currentPtr, uint32_t remainSize, double *numberValuePtr) +{ + RyanJsonCheckAssert(NULL != currentPtr && NULL != numberValuePtr); + RyanJsonCheckAssert(remainSize > 0); + + RyanJsonBool_e isInt = RyanJsonTrue; + RyanJsonParseBuffer parseBuf = {.currentPtr = currentPtr, .remainSize = remainSize}; + return RyanJsonInternalParseDouble(&parseBuf, numberValuePtr, &isInt); +} diff --git a/RyanJson/RyanJsonPrint.c b/RyanJson/RyanJsonPrint.c new file mode 100644 index 0000000..855e5eb --- /dev/null +++ b/RyanJson/RyanJsonPrint.c @@ -0,0 +1,549 @@ +#include "RyanJsonInternal.h" + +typedef struct +{ + uint8_t *bufAddress; // 打印输出缓冲区地址 + uint32_t cursor; // 当前写入位置(字节偏移) + uint32_t size; // 缓冲区总容量,禁止扩容时写满即返回失败 + RyanJsonBool_e isNoAlloc; // 是否禁止动态扩容(True 表示不扩容) +} RyanJsonPrintBuffer; + +#define printBufPutChar(printfBuf, char) \ + do \ + { \ + ((printfBuf)->bufAddress[(printfBuf)->cursor++] = (char)); \ + } while (0) +#define printBufPutString(printfBuf, putStr, putStrLen) \ + do \ + { \ + for (uint32_t putStrCount = 0; putStrCount < (uint32_t)(putStrLen); putStrCount++) \ + { \ + printBufPutChar(printfBuf, (putStr)[putStrCount]); \ + } \ + } while (0) +#define printBufCurrentPtr(printfBuf) (&((printfBuf)->bufAddress[(printfBuf)->cursor])) +#define printBufRemainBytes(printfBuf) ((printfBuf)->size - (printfBuf)->cursor) + +/** + * @brief 检查并扩展打印缓冲区容量 + */ +static RyanJsonBool_e RyanJsonPrintBufAppend(RyanJsonPrintBuffer *printfBuf, uint32_t needed) +{ + RyanJsonCheckAssert(NULL != printfBuf && NULL != printfBuf->bufAddress); + + needed += printfBuf->cursor; + + // 当前缓冲区空间充足 + if (needed <= printfBuf->size) { return RyanJsonTrue; } + + // 禁止动态扩容 + RyanJsonCheckReturnFalse(RyanJsonFalse == printfBuf->isNoAlloc); + + uint32_t size = needed + RyanJsonPrintfPreAlloSize; + char *address = (char *)RyanJsonInternalExpandRealloc(printfBuf->bufAddress, printfBuf->size, size); + RyanJsonCheckReturnFalse(NULL != address); + + printfBuf->size = size; + printfBuf->bufAddress = (uint8_t *)address; + return RyanJsonTrue; +} + +/** + * @brief 规范化浮点数输出:删除尾部无效的0(非科学计数法时) + */ +static int32_t RyanJsonTrimDoubleTrailingZeros(RyanJsonPrintBuffer *printfBuf, int32_t len) +{ + // Linux 测试环境:偶尔把 'e' 改为 'E',覆盖大小写兼容分支 +#ifdef RyanJsonLinuxTestEnv + int32_t eIndex = INT32_MIN; + if (RyanJsonFuzzerShouldFail(20)) + { + for (int32_t i = 0; i < len; i++) + { + if ('e' == printBufCurrentPtr(printfBuf)[i]) + { + printBufCurrentPtr(printfBuf)[i] = 'E'; + eIndex = i; + break; + } + } + } +#endif + + // 检查是否使用科学计数法 + RyanJsonBool_e isScientificNotation = RyanJsonFalse; + for (int32_t i = 0; i < len; i++) + { + // 有些平台会输出'E' + if ('e' == printBufCurrentPtr(printfBuf)[i] || 'E' == printBufCurrentPtr(printfBuf)[i]) + { + isScientificNotation = RyanJsonTrue; + break; + } + } + + // 恢复测试环境临时改写的大写 'E' +#ifdef RyanJsonLinuxTestEnv + if (INT32_MIN != eIndex) { printBufCurrentPtr(printfBuf)[eIndex] = 'e'; } +#endif + + if (RyanJsonFalse == isScientificNotation) + { + // 删除小数部分中无效的 0 + // 最小也要为"0.0" + while (len > 3) + { + if ('0' != printBufCurrentPtr(printfBuf)[len - 1]) { break; } + if ('.' == printBufCurrentPtr(printfBuf)[len - 2]) { break; } + len--; + printBufCurrentPtr(printfBuf)[len] = '\0'; + } + } + + return len; +} + +/** + * @brief 打印数字节点 + */ +static RyanJsonBool_e RyanJsonPrintNumber(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf) +{ + RyanJsonCheckAssert(NULL != pJson && NULL != printfBuf); + + int32_t len; + + // Number 节点按 int32_t 存储 + if (RyanJsonFalse == RyanJsonGetPayloadNumberIsDoubleByFlag(pJson)) + { + // INT32_MIN = -2147483648 (11 chars) + '\0' + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, 12)); + + len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf), "%" PRId32, + RyanJsonGetIntValue(pJson)); + // 这里前面已保证至少 12 字节空间(INT32_MIN + '\0'),正常实现下不会截断 + RyanJsonCheckReturnFalse(len > 0); + printfBuf->cursor += (uint32_t)len; + + return RyanJsonTrue; + } + + // Number 节点按 double 存储 + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, RyanJsonDoubleBufferSize)); + double doubleValue = RyanJsonGetDoubleValue(pJson); + + // 处理特殊值:无穷大和 NaN 输出为 null(RFC 8259 不支持 Infinity/NaN) + if (isinf(doubleValue) || isnan(doubleValue)) + { + printBufPutString(printfBuf, (uint8_t *)"null", 4); + return RyanJsonTrue; + } + + double absDoubleValue = fabs(doubleValue); + + // 判断是否可按整数样式输出,并保留一位小数(例如 5.0、0.0) + // 注意:0 也需要特殊处理,否则会进入科学记数法分支 + // 在有界空间内使用完全变换 + if ((absDoubleValue < DBL_EPSILON || (absDoubleValue < 1.0e15 && absDoubleValue >= 1.0e-6)) && + fabs(floor(doubleValue) - doubleValue) <= DBL_EPSILON) + { + len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf), "%.1lf", doubleValue); + // 有外层限制 1e-6 ~ 1e15, 所以肯定不会越界 + RyanJsonCheckReturnFalse(len > 0); + + // 嵌入式场景下缓冲区可能偏小,保留额外边界检查 +#ifndef RyanJsonLinuxTestEnv + RyanJsonCheckReturnFalse(len < (int32_t)printBufRemainBytes(printfBuf)); +#endif + } + else + { + // Linux 测试环境:在两种格式间切换,覆盖去零与回读校验分支 +#ifdef RyanJsonLinuxTestEnv +#undef RyanJsonSnprintfSupportScientific + // 基于 double 值本身选择格式(确定性),保证同一个值总是用相同格式 + // %lf 在 [1e-6, 1e6] 范围内输出安全 + // 极端值(>1e6 或 <1e-6)必须使用科学记数法,否则可能超出缓冲区 + RyanJsonBool_e RyanJsonSnprintfSupportScientific = doubleValue > 1.0 ? RyanJsonTrue : RyanJsonFalse; + +#endif + + // 极大/极小数或普通浮点数 + // 不使用 %.15g 是因为很多嵌入式平台上 %.15g 与 %.17g 效果接近 + // 可能出现 0.2 -> 0.200000003000000,即便去掉尾部 0 仍不美观 + // 使用 %lf 存在缓冲区压力,但在当前嵌入式目标上可接受 + len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf), + RyanJsonSnprintfSupportScientific ? "%.15g" : "%lf", doubleValue); +#ifdef isEnableFuzzer + // 测试环境:偶尔模拟溢出以触发防御性检查分支 + if (RyanJsonFuzzerShouldFail(1000) && len > 0) { len = (int32_t)printBufRemainBytes(printfBuf) + 1; } +#endif + RyanJsonCheckReturnFalse(len > 0 && len < (int32_t)printBufRemainBytes(printfBuf)); + + // 往返检查:在去0之前进行,确保原始精度足够 + // 如果精度不够,改用 %.17g + double number = 0; + RyanJsonCheckReturnFalse(RyanJsonTrue == + RyanJsonInternalParseDoubleRaw(printBufCurrentPtr(printfBuf), (uint32_t)len, &number)); + if (RyanJsonFalse == RyanJsonCompareDouble(number, doubleValue)) + { + len = RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), printBufRemainBytes(printfBuf), "%.17g", doubleValue); + RyanJsonCheckReturnFalse(len > 0); + +#ifndef RyanJsonLinuxTestEnv + // "%.17g" 也做边界检查,避免平台实现差异导致越界 + RyanJsonCheckReturnFalse(len < (int32_t)printBufRemainBytes(printfBuf)); +#endif + } + + // 删除尾部无效 0。理论上主要作用于 %lf,但统一处理更稳妥 + len = RyanJsonTrimDoubleTrailingZeros(printfBuf, len); + } + + printfBuf->cursor += (uint32_t)len; + return RyanJsonTrue; +} + +/** + * @brief 打印字符串并执行转义 + */ +static RyanJsonBool_e RyanJsonPrintStringBuffer(const uint8_t *strValue, RyanJsonPrintBuffer *printfBuf) +{ + RyanJsonCheckAssert(NULL != strValue && NULL != printfBuf); + // 获取长度 + const uint8_t *strCurrentPtr = strValue; + uint32_t escapeCharCount = 0; + for (strCurrentPtr = strValue; *strCurrentPtr; strCurrentPtr++) + { + switch (*strCurrentPtr) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + case '/': escapeCharCount++; break; + + default: + // 按最坏情况预留转义空间,保证后续写入安全 + if (*strCurrentPtr < 32) { escapeCharCount += 5; } + break; + } + } + + RyanJsonCheckReturnFalse( + RyanJsonPrintBufAppend(printfBuf, (uint32_t)(strCurrentPtr - strValue) + escapeCharCount + 2U)); // 最小是\" \" + printBufPutChar(printfBuf, '\"'); + + // 没有转义字符 + if (0 == escapeCharCount) + { + printBufPutString(printfBuf, strValue, (strCurrentPtr - strValue)); + printBufPutChar(printfBuf, '\"'); + return RyanJsonTrue; + } + + strCurrentPtr = strValue; + while (*strCurrentPtr) + { + if ((*strCurrentPtr) >= ' ' && '\"' != *strCurrentPtr && '\\' != *strCurrentPtr) + { + printBufPutChar(printfBuf, *strCurrentPtr++); + continue; + } + + // 转义和打印 + printBufPutChar(printfBuf, '\\'); + + switch (*strCurrentPtr) + { + case '\\': printBufPutChar(printfBuf, '\\'); break; + case '\"': printBufPutChar(printfBuf, '\"'); break; + case '\b': printBufPutChar(printfBuf, 'b'); break; + case '\f': printBufPutChar(printfBuf, 'f'); break; + case '\n': printBufPutChar(printfBuf, 'n'); break; + case '\r': printBufPutChar(printfBuf, 'r'); break; + case '\t': printBufPutChar(printfBuf, 't'); break; + + default: { + // 这里无需额外校验输入字节有效性,RyanJson 已保证转义序列合法 + RyanJsonCheckReturnFalse(5 == RyanJsonSnprintf((char *)printBufCurrentPtr(printfBuf), + printBufRemainBytes(printfBuf), "u%04X", *strCurrentPtr)); + printfBuf->cursor += 5; // uXXXX 四位十六进制编码 + break; + } + } + strCurrentPtr++; + } + + printBufPutChar(printfBuf, '\"'); + + return RyanJsonTrue; +} + +/** + * @brief 打印节点中的 strValue + * + * @param pJson 字符串节点 + * @param printfBuf 打印缓冲区 + * @return RyanJsonBool_e 打印是否成功 + */ +static RyanJsonBool_e RyanJsonPrintString(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf) +{ + RyanJsonCheckAssert(NULL != pJson && NULL != printfBuf); + return RyanJsonPrintStringBuffer((const uint8_t *)RyanJsonGetStringValue(pJson), printfBuf); +} + +/** + * @brief 将 Json 树打印为字符串(迭代实现) + */ +static RyanJsonBool_e RyanJsonPrintValue(RyanJson_t pJson, RyanJsonPrintBuffer *printfBuf, uint32_t depthStart, + const RyanJsonPrintStyle *style) +{ + RyanJsonCheckAssert(NULL != pJson && NULL != printfBuf); + + RyanJson_t curr = pJson; + uint32_t depth = depthStart; + + // 无需显式栈:通过线索化链表与容器状态完成遍历与回溯 + while (1) + { + // 打印 key(当前节点包含 key 时) + if (curr != pJson && RyanJsonIsKey(curr)) + { + if (style->format) + { + uint32_t needed = depth * style->indentLen; + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, needed)); + for (uint32_t i = 0; i < depth; i++) + { + printBufPutString(printfBuf, (uint8_t *)style->indent, style->indentLen); + } + } + + RyanJsonCheckReturnFalse(RyanJsonPrintStringBuffer((const uint8_t *)RyanJsonGetKey(curr), printfBuf)); + + // 打印冒号和空格 + uint32_t spaceLen = style->format ? style->spaceAfterColon : 0; + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, 1 + spaceLen)); + printBufPutChar(printfBuf, ':'); + if (style->format) + { + for (uint32_t i = 0; i < spaceLen; i++) + { + printBufPutChar(printfBuf, ' '); + } + } + } + else if (curr != pJson && style->format) + { + // 数组元素缩进(没有 key) + uint32_t needed = depth * style->indentLen; + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, needed)); + for (uint32_t i = 0; i < depth; i++) + { + printBufPutString(printfBuf, (uint8_t *)style->indent, style->indentLen); + } + } + + // 打印 Value(标量值或容器起始符) + RyanJsonType_e type = RyanJsonGetType(curr); + switch (type) + { + case RyanJsonTypeNull: + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, 4)); + printBufPutString(printfBuf, (uint8_t *)"null", 4); + break; + + case RyanJsonTypeBool: { + RyanJsonBool_e val = RyanJsonGetBoolValue(curr); + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, val ? 4 : 5)); + printBufPutString(printfBuf, val ? (uint8_t *)"true" : (uint8_t *)"false", val ? 4 : 5); + break; + } + + case RyanJsonTypeNumber: RyanJsonCheckReturnFalse(RyanJsonPrintNumber(curr, printfBuf)); break; + + case RyanJsonTypeString: RyanJsonCheckReturnFalse(RyanJsonPrintString(curr, printfBuf)); break; + + case RyanJsonTypeArray: + case RyanJsonTypeObject: { + RyanJsonBool_e currIsObject = (type == RyanJsonTypeObject); + RyanJson_t currChild = RyanJsonGetObjectValue(curr); + + // 空容器直接输出 [] 或 {} + if (NULL == currChild) + { + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, 2)); + printBufPutChar(printfBuf, currIsObject ? '{' : '['); + printBufPutChar(printfBuf, currIsObject ? '}' : ']'); + } + // 非空容器进入子节点处理 + else + { + uint32_t newlineLen = style->format ? style->newlineLen : 0; + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, 1 + newlineLen)); // '[' + newline + printBufPutChar(printfBuf, currIsObject ? '{' : '['); + + // 开启 format 后,非空容器统一走多行输出 + if (style->format) { printBufPutString(printfBuf, (uint8_t *)style->newline, newlineLen); } + + curr = currChild; + depth++; + continue; // 直接跳转到下一次循环处理 Child + } + break; + } + + default: return RyanJsonFalse; + } + + // 处理逗号、兄弟节点切换与回溯闭合 + if (curr == pJson) { return RyanJsonTrue; } + + do + { + // 有兄弟节点时输出逗号并切换到兄弟 + RyanJson_t nextInfo = RyanJsonGetNext(curr); // 能够处理 IsLast + if (nextInfo) + { + uint32_t newlineLen = style->format ? style->newlineLen : 0; + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, 1 + newlineLen)); // ',' + newline + printBufPutChar(printfBuf, ','); + + if (style->format) { printBufPutString(printfBuf, (uint8_t *)style->newline, newlineLen); } + + curr = nextInfo; + break; // 处理新的 curr(兄弟节点) + } + + // 无兄弟节点时回溯到父节点并闭合容器 + // 利用线索化特性:IsLast 节点的 next 指向父节点 + curr = curr->next; + depth--; + + // 打印结束括号前的缩进(仅 format 模式) + if (style->format) + { + uint32_t needed = style->newlineLen + depth * style->indentLen; + + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, needed)); + printBufPutString(printfBuf, (uint8_t *)style->newline, style->newlineLen); + for (uint32_t i = 0; i < depth; i++) + { + printBufPutString(printfBuf, (uint8_t *)style->indent, style->indentLen); + } + } + + RyanJsonCheckReturnFalse(RyanJsonPrintBufAppend(printfBuf, 1)); + printBufPutChar(printfBuf, RyanJsonIsArray(curr) ? ']' : '}'); + + // 如果回溯到了起始根节点,结束打印 + if (curr == pJson) { return RyanJsonTrue; } + + // 继续循环,检查父节点是否仍有同层兄弟 + } while (1); + } +} + +/** + * @brief 按指定风格打印 Json(动态分配输出缓冲) + * + * @param pJson 待打印节点 + * @param preset 初始缓冲大小 + * @param style 打印风格 + * @param len 输出长度,可为 NULL + * @return char* 打印结果,需调用 RyanJsonFree 释放 + */ +char *RyanJsonPrintWithStyle(RyanJson_t pJson, uint32_t preset, const RyanJsonPrintStyle *style, uint32_t *len) +{ + RyanJsonCheckReturnNull(NULL != pJson && NULL != style); + + RyanJsonPrintBuffer printfBuf = { + .isNoAlloc = RyanJsonFalse, + .size = preset, + .cursor = 0, + }; + + if (printfBuf.size < RyanJsonPrintfPreAlloSize) { printfBuf.size = RyanJsonPrintfPreAlloSize; } + printfBuf.bufAddress = (uint8_t *)jsonMalloc(printfBuf.size); + RyanJsonCheckReturnNull(NULL != printfBuf.bufAddress); + + RyanJsonCheckCode(RyanJsonTrue == RyanJsonPrintValue(pJson, &printfBuf, 0, style), { + jsonFree(printfBuf.bufAddress); + return NULL; + }); + + RyanJsonCheckCode(RyanJsonPrintBufAppend(&printfBuf, 1), { + jsonFree(printfBuf.bufAddress); + return NULL; + }); + + printfBuf.bufAddress[printfBuf.cursor] = '\0'; + if (len) { *len = printfBuf.cursor; } + + return (char *)printfBuf.bufAddress; +} + +/** + * @brief 使用默认风格打印 Json(动态分配输出缓冲) + * + * @param pJson 待打印节点 + * @param preset 初始缓冲大小 + * @param format 是否格式化输出 + * @param len 输出长度,可为 NULL + * @return char* 打印结果,需调用 RyanJsonFree 释放 + */ +char *RyanJsonPrint(RyanJson_t pJson, uint32_t preset, RyanJsonBool_e format, uint32_t *len) +{ + RyanJsonPrintStyle style = { + .indent = "\t", .newline = "\n", .indentLen = 1, .newlineLen = 1, .spaceAfterColon = 1, .format = format}; + return RyanJsonPrintWithStyle(pJson, preset, &style, len); +} + +/** + * @brief 按指定风格打印 Json(使用外部预分配缓冲) + * + * @param pJson 待打印节点 + * @param buffer 外部缓冲区 + * @param length 缓冲区大小 + * @param style 打印风格 + * @param len 输出长度,可为 NULL + * @return char* 成功返回 buffer,失败返回 NULL + */ +char *RyanJsonPrintPreallocatedWithStyle(RyanJson_t pJson, char *buffer, uint32_t length, const RyanJsonPrintStyle *style, uint32_t *len) +{ + RyanJsonCheckReturnNull(NULL != pJson && NULL != buffer && NULL != style && length > 0); + + RyanJsonPrintBuffer printfBuf = { + .bufAddress = (uint8_t *)buffer, + .isNoAlloc = RyanJsonTrue, + .size = length, + .cursor = 0, + }; + + RyanJsonCheckReturnNull(RyanJsonTrue == RyanJsonPrintValue(pJson, &printfBuf, 0, style)); + RyanJsonCheckReturnNull(RyanJsonPrintBufAppend(&printfBuf, 1)); + printfBuf.bufAddress[printfBuf.cursor] = '\0'; + if (len) { *len = printfBuf.cursor; } + + return (char *)printfBuf.bufAddress; +} + +/** + * @brief 使用默认风格打印 Json(使用外部预分配缓冲) + * + * @param pJson 待打印节点 + * @param buffer 外部缓冲区 + * @param length 缓冲区大小 + * @param format 是否格式化输出 + * @param len 输出长度,可为 NULL + * @return char* 成功返回 buffer,失败返回 NULL + */ +char *RyanJsonPrintPreallocated(RyanJson_t pJson, char *buffer, uint32_t length, RyanJsonBool_e format, uint32_t *len) +{ + RyanJsonPrintStyle style = { + .indent = "\t", .newline = "\n", .indentLen = 1, .newlineLen = 1, .spaceAfterColon = 1, .format = format}; + return RyanJsonPrintPreallocatedWithStyle(pJson, buffer, length, &style, len); +} diff --git a/RyanJson/RyanJsonUtils.c b/RyanJson/RyanJsonUtils.c index ca6de9f..737f8f0 100644 --- a/RyanJson/RyanJsonUtils.c +++ b/RyanJson/RyanJsonUtils.c @@ -1,12 +1,457 @@ -#include "RyanJsonUtils.h" +#include "RyanJsonInternal.h" + +#ifdef RyanJsonLinuxTestEnv +#include +#include + +#undef RyanJsonSnprintf +/** + * @brief Linux 测试环境下的 snprintf 包装(支持 fuzzer 注入) + */ +RyanJsonInternalApi int32_t RyanJsonSnprintf(char *buf, size_t size, const char *fmt, ...) +{ +#ifdef isEnableFuzzer + // Fuzzer 模式:随机触发失败,测试错误处理路径 + if (RyanJsonFuzzerShouldFail(500)) { return 0; } +#endif + + va_list args; + va_start(args, fmt); + int32_t ret = vsnprintf(buf, size, fmt, args); + va_end(args); + return ret; +} +#endif // RyanJsonLinuxTestEnv + +/** + * @brief 比较两个 C 字符串是否相等 + * + * @param s1 字符串1 + * @param s2 字符串2 + * @return RyanJsonBool_e 是否相等 + */ +RyanJsonInternalApi RyanJsonBool_e RyanJsonInternalStrEq(const char *s1, const char *s2) +{ + // NULL 检查 + RyanJsonCheckAssert(NULL != s1 && NULL != s2); + + // 地址相同 + if (s1 == s2) { return RyanJsonTrue; } + + // 首字符不同 + if (*s1 != *s2) { return RyanJsonFalse; } + + return RyanJsonMakeBool(0 == RyanJsonStrcmp(s1, s2)); +} + +/** + * @brief 安全的浮点数比较 + */ +RyanJsonBool_e RyanJsonCompareDouble(double a, double b) +{ + double diff = fabs(a - b); + double absA = fabs(a); + double absB = fabs(b); + double maxVal = (absA > absB ? absA : absB); + + // 允许的容差:相对误差 + 绝对误差 + double epsilon = DBL_EPSILON * maxVal; + double absTolerance = RyanJsonAbsTolerance; // 绝对容差 + + return diff <= (epsilon > absTolerance ? epsilon : absTolerance); +} + +/** + * @brief 获取字符串指针模式的缓冲区地址 + * + * @param pJson Json 节点 + * @return uint8_t* 缓冲区首地址 + */ +RyanJsonInternalApi uint8_t *RyanJsonInternalGetStrPtrModeBuf(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + + // 使用 memcpy 规避潜在的非对齐访问警告 + void *tmpPtr = NULL; + RyanJsonMemcpy((void *)&tmpPtr, (RyanJsonGetPayloadPtr(pJson) + RyanJsonFlagSize + RyanJsonKeyFeidLenMaxSize), sizeof(void *)); + return (uint8_t *)tmpPtr; +} + +/** + * @brief 设置字符串指针模式的缓冲区地址 + * + * @param pJson Json 节点 + * @param heapPtr 缓冲区首地址 + */ +RyanJsonInternalApi void RyanJsonInternalSetStrPtrModeBuf(RyanJson_t pJson, uint8_t *heapPtr) +{ + RyanJsonCheckAssert(NULL != pJson); + RyanJsonCheckAssert(NULL != heapPtr); + + // 使用 memcpy 规避潜在的非对齐访问警告 + void *tmpPtr = heapPtr; + // 前面依次是 flag 与 key 长度字段空间,这里只写入堆指针 + RyanJsonMemcpy((RyanJsonGetPayloadPtr(pJson) + RyanJsonFlagSize + RyanJsonKeyFeidLenMaxSize), (const void *)&tmpPtr, + sizeof(void *)); +} + +/** + * @brief 获取字符串指针模式指定偏移处的地址 + * + * @param pJson Json 节点 + * @param index 索引 + * @return uint8_t* 偏移后的地址 + */ +RyanJsonInternalApi uint8_t *RyanJsonInternalGetStrPtrModeBufAt(RyanJson_t pJson, uint32_t index) +{ + RyanJsonCheckAssert(NULL != pJson); + return (uint8_t *)(RyanJsonInternalGetStrPtrModeBuf(pJson) + (index)); +} + +/** + * @brief 计算 key 长度需要的字节数 + * + * @param len key 长度 + * @return uint8_t 需要的字节数 + */ +RyanJsonInternalApi uint8_t RyanJsonInternalCalcLenBytes(uint32_t len) +{ + if (len <= UINT8_MAX) { return 1; } // 01: 1 byte + if (len <= UINT16_MAX) { return 2; } // 10: 2 bytes + return 3; // 11: 4 bytes +} + +/** + * @brief 解码 key 长度字段 + * + * @param encoded 编码后的 key 长度字段 + * @return uint8_t 解码后的 key 长度 + */ +RyanJsonInternalApi uint8_t RyanJsonInternalDecodeKeyLenField(uint8_t encoded) +{ + return 3 == encoded ? 4 : encoded; +} + +/** + * @brief 设置 key 长度 + * + * @param pJson Json 节点 + * @param value key 长度 + */ +static void RyanJsonSetKeyLen(RyanJson_t pJson, uint32_t value) +{ + RyanJsonCheckAssert(NULL != pJson); + uint8_t *buf = RyanJsonGetPayloadPtr(pJson) + RyanJsonFlagSize; + uint8_t keyFieldLen = RyanJsonInternalDecodeKeyLenField(RyanJsonGetPayloadEncodeKeyLenByFlag(pJson)); + RyanJsonCheckAssert(keyFieldLen <= RyanJsonKeyFeidLenMaxSize); + + // 使用大小端无关的方式写入 + for (uint8_t i = 0; i < keyFieldLen; i++) + { + buf[i] = (uint8_t)(value & 0xFF); + value >>= 8; + } +} + +/** + * @brief 获取节点 key 的长度 + * + * @param pJson Json 节点 + * @return uint32_t key 长度 + */ +RyanJsonInternalApi uint32_t RyanJsonInternalGetKeyLen(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + uint8_t *buf = RyanJsonGetPayloadPtr(pJson) + RyanJsonFlagSize; + uint8_t keyFieldLen = RyanJsonInternalDecodeKeyLenField(RyanJsonGetPayloadEncodeKeyLenByFlag(pJson)); + RyanJsonCheckAssert(keyFieldLen <= RyanJsonKeyFeidLenMaxSize); + + // 使用大小端无关的方式读取 + uint32_t value = 0; + for (uint8_t i = 0; i < keyFieldLen; i++) + { + value |= ((uint32_t)buf[i]) << (i * 8); + } + return value; +} + +/** + * @brief 获取节点 value 存储区地址 + * + * @param pJson Json 节点 + * @return void* value 地址 + */ +RyanJsonInternalApi void *RyanJsonInternalGetValue(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + + uint32_t len = RyanJsonFlagSize; + if (RyanJsonIsKey(pJson)) { len += RyanJsonInlineStringSize; } + return RyanJsonGetPayloadPtr(pJson) + len; +} + +/** + * @brief 更新 key 与 strValue + * + * @param pJson Json 节点 + * @param isNew 是否是新创建的节点 + * @param key key 字符串 + * @param strValue strValue + * @return RyanJsonBool_e + */ +RyanJsonInternalApi RyanJsonBool_e RyanJsonInternalChangeString(RyanJson_t pJson, RyanJsonBool_e isNew, const char *key, + const char *strValue) +{ + RyanJsonCheckAssert(NULL != pJson); + + uint32_t keyLen = 0; // key 字节长度 + uint8_t keyLenField = 0; // key 长度字段编码所需字节数 + uint32_t strValueLen = 0; // strValue 字节长度 + + uint32_t mallocSize = 0; + + // 计算 str 缓冲区所需的总字节数 + if (NULL != key) + { + keyLen = RyanJsonStrlen(key); + keyLenField = RyanJsonInternalCalcLenBytes(keyLen); + mallocSize += keyLen + 1; + } + + if (NULL != strValue) + { + strValueLen = RyanJsonStrlen(strValue); + mallocSize += strValueLen + 1; + } + if (0 == mallocSize) { return RyanJsonTrue; } + + // 记录旧 str 缓冲区,切换成功后再释放 + uint8_t *oldPrt = NULL; + if (RyanJsonFalse == isNew) + { + if (RyanJsonTrue == RyanJsonGetPayloadStrIsPtrByFlag(pJson)) { oldPrt = RyanJsonInternalGetStrPtrModeBuf(pJson); } + } + + char arr[RyanJsonInlineStringSize]; + + // 若 key + value + keyLenField 编码都能放进内联区,则走内联存储 + if ((mallocSize + RyanJsonInternalDecodeKeyLenField(keyLenField)) <= RyanJsonInlineStringSize) + { + RyanJsonSetPayloadStrIsPtrByFlag(pJson, RyanJsonFalse); + // 先写入临时缓冲区,避免切换内联模式时出现覆盖 + if (keyLen) { RyanJsonMemcpy(arr, key, keyLen); } + if (strValueLen) { RyanJsonMemcpy(arr + keyLen, strValue, strValueLen); } + } + else + { + // 申请新的 str 缓冲区 + uint8_t *newPtr = (uint8_t *)jsonMalloc(mallocSize); + RyanJsonCheckReturnFalse(NULL != newPtr); + + // 先拷贝内容,再写回指针,避免指针写入后覆盖原数据 + if (NULL != key) + { + if (0 != keyLen) { RyanJsonMemcpy(newPtr, key, keyLen); } + newPtr[keyLen] = '\0'; + } + + if (NULL != strValue) + { + uint8_t *strValueBuf = newPtr; + if (NULL != key) { strValueBuf = newPtr + keyLen + 1; } + + if (0 != strValueLen) { RyanJsonMemcpy(strValueBuf, strValue, strValueLen); } + strValueBuf[strValueLen] = '\0'; + } + + RyanJsonInternalSetStrPtrModeBuf(pJson, newPtr); + RyanJsonSetPayloadStrIsPtrByFlag(pJson, RyanJsonTrue); + } + + // 设置 key + if (NULL != key) + { + RyanJsonSetPayloadEncodeKeyLenByFlag(pJson, keyLenField); + RyanJsonSetKeyLen(pJson, keyLen); + if (RyanJsonFalse == RyanJsonGetPayloadStrIsPtrByFlag(pJson)) + { + char *keyBuf = RyanJsonGetKey(pJson); + if (0 != keyLen) { RyanJsonMemcpy(keyBuf, arr, keyLen); } + keyBuf[keyLen] = '\0'; + } + } + else + { + RyanJsonSetPayloadEncodeKeyLenByFlag(pJson, 0); + } + + // 设置 strValue + if (NULL != strValue) + { + if (RyanJsonFalse == RyanJsonGetPayloadStrIsPtrByFlag(pJson)) + { + char *strValueBuf = RyanJsonGetStringValue(pJson); + if (0 != strValueLen) { RyanJsonMemcpy(strValueBuf, arr + keyLen, strValueLen); } + strValueBuf[strValueLen] = '\0'; + } + } + + if (oldPrt) { jsonFree(oldPrt); } + return RyanJsonTrue; +} + +/** + * @brief 创建一个节点 + * + * @param info 节点信息 + * @return RyanJson_t 节点 + */ +RyanJsonInternalApi RyanJson_t RyanJsonInternalNewNode(RyanJsonNodeInfo_t *info) +{ + RyanJsonCheckAssert(NULL != info); + + // 加1是flag的空间 + uint32_t size = sizeof(struct RyanJsonNode) + RyanJsonFlagSize; + + switch (info->type) + { + case RyanJsonTypeNumber: + if (RyanJsonFalse == info->numberIsDoubleFlag) { size += sizeof(int32_t); } + else + { + size += sizeof(double); + } + break; + case RyanJsonTypeArray: + case RyanJsonTypeObject: size += sizeof(RyanJson_t); break; + + default: break; + } + + // 是否内联字符串 + if (NULL != info->key || RyanJsonTypeString == info->type) { size += RyanJsonInlineStringSize; } + + RyanJson_t pJson = (RyanJson_t)jsonMalloc((size_t)size); + RyanJsonCheckReturnNull(NULL != pJson); + + // 节点体积较小,直接整块清零即可 + RyanJsonMemset(pJson, 0, size); + + RyanJsonSetType(pJson, info->type); + + // 设置 key 和 value + RyanJsonCheckCode(RyanJsonTrue == RyanJsonInternalChangeString(pJson, RyanJsonTrue, info->key, info->strValue), { + jsonFree(pJson); + return NULL; + }); + + // 设置 bool / number + if (RyanJsonTypeBool == info->type) { RyanJsonSetPayloadBoolValueByFlag(pJson, info->boolIsTrueFlag); } + else if (RyanJsonTypeNumber == info->type) { RyanJsonSetPayloadNumberIsDoubleByFlag(pJson, info->numberIsDoubleFlag); } + + return pJson; +} /** - * @brief 连续通过 key 获取json对象的子项 + * @brief 在父节点中插入子节点(维护线索化链表) * - * @param pJson - * @param key - * @param ... 可变参,连续输入key,直到NULL结束 - * @return RyanJson_t + * @param parent 父节点(Object 或 Array) + * @param prev 前驱兄弟节点,为 NULL 表示头插 + * @param item 待插入节点 + */ +RyanJsonInternalApi void RyanJsonInternalListInsertAfter(RyanJson_t parent, RyanJson_t prev, RyanJson_t item) +{ + RyanJson_t nextItem; + + if (prev) + { + // 若 prev 原本是尾节点,则 prev->next 指向 parent,此时 nextItem 应视为 NULL + // 通过 IsLast 标志区分“下一个兄弟节点”与“父节点线索”。 + if (RyanJsonGetPayloadIsLastByFlag(prev)) { nextItem = NULL; } + else + { + nextItem = prev->next; + } + } + else + { + // 插入到头部。RyanJsonNode 结构保证了 Object 和 Array 的 Value 指针位置一致 + nextItem = RyanJsonGetObjectValue(parent); + } + + // 先链接 prev -> item + if (prev) + { + prev->next = item; + // prev 不再是最后一个,取消 IsLast 标记 + RyanJsonSetPayloadIsLastByFlag(prev, 0); + } + else + { + // 只有 item 一个子节点,或者 item 是新的头部 + RyanJsonInternalChangeObjectValue(parent, item); + } + + // 再链接 item -> nextItem(或 Parent) + if (nextItem) + { + // 不是最后一个节点,item->next 指向下一个兄弟 + item->next = nextItem; + RyanJsonSetPayloadIsLastByFlag(item, 0); + } + else + { + // 是最后一个节点,item->next 指向 Parent (线索化) + item->next = parent; + RyanJsonSetPayloadIsLastByFlag(item, 1); + } +} + +/** + * @brief 获取同层下一个兄弟节点 + * + * @param pJson 当前节点 + * @return RyanJson_t 下一个兄弟节点;若当前为最后节点返回 NULL + */ +RyanJson_t RyanJsonGetNext(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + + // 尾节点(Bit7=1)的 next 保存的是父节点线索,对外应返回 NULL + if (RyanJsonGetPayloadIsLastByFlag(pJson)) { return NULL; } + return pJson->next; +} + +/** + * @brief 获取当前节点的父节点 + * + * @param pJson 当前节点 + * @return RyanJson_t 父节点 + */ +RyanJsonInternalApi RyanJson_t RyanJsonInternalGetParent(RyanJson_t pJson) +{ + RyanJsonCheckAssert(NULL != pJson); + + RyanJson_t curr = pJson; + RyanJson_t next = RyanJsonGetNext(curr); + + // 沿着兄弟链表一直走到该层级的最后一个节点 + while (NULL != next) + { + curr = next; + next = RyanJsonGetNext(curr); + } + + // 此时 curr 是当前层级的最后一个节点,其 next 指针存放的就是父节点 + return curr->next; +} + +/** + * @brief 按多级 key 路径获取节点 + * + * @param pJson 起始节点 + * @param key 第一级 key + * @return RyanJson_t 目标节点,失败返回 NULL */ RyanJson_t RyanJsonGetObjectByKeys(RyanJson_t pJson, const char *key, ...) { @@ -14,7 +459,8 @@ RyanJson_t RyanJsonGetObjectByKeys(RyanJson_t pJson, const char *key, ...) const char *nextKey = key; RyanJson_t nextItem = RyanJsonGetObjectByKey(pJson, nextKey); - RyanJsonCheckReturnNull(NULL != nextItem && RyanJsonIsKey(nextItem)); + RyanJsonCheckReturnNull(NULL != nextItem); + RyanJsonCheckAssert(RyanJsonIsKey(nextItem)); va_list args; va_start(args, key); @@ -30,12 +476,11 @@ RyanJson_t RyanJsonGetObjectByKeys(RyanJson_t pJson, const char *key, ...) } /** - * @brief 连续通过 索引 获取json对象的子项 + * @brief 按多级 index 路径获取节点 * - * @param pJson - * @param index - * @param ... 可变参,连续输入索引,直到UINT32_MAX结束 - * @return RyanJson_t + * @param pJson 起始节点 + * @param index 第一级索引 + * @return RyanJson_t 目标节点,失败返回 NULL */ RyanJson_t RyanJsonGetObjectByIndexs(RyanJson_t pJson, uint32_t index, ...) { @@ -59,106 +504,70 @@ RyanJson_t RyanJsonGetObjectByIndexs(RyanJson_t pJson, uint32_t index, ...) } /** - * @brief 创建一个int类型的数组json对象 + * @brief 创建 int32_t 数组节点 * - * @param numbers 数组的地址必须为int类型 - * @param count 数组的长度 - * @return RyanJson_t + * @param numbers 输入数组 + * @param count 元素个数 + * @return RyanJson_t 新建数组节点,失败返回 NULL */ RyanJson_t RyanJsonCreateIntArray(const int32_t *numbers, uint32_t count) { RyanJsonCheckReturnNull(NULL != numbers); RyanJson_t pJson = RyanJsonCreateArray(); - for (uint32_t i = 0; pJson && i < count; i++) { RyanJsonAddIntToArray(pJson, numbers[i]); } + RyanJsonCheckReturnNull(NULL != pJson); + for (uint32_t i = 0; i < count; i++) + { + RyanJsonCheckCode(RyanJsonTrue == RyanJsonAddIntToArray(pJson, numbers[i]), { + RyanJsonDelete(pJson); + return NULL; + }); + } return pJson; } /** - * @brief 创建一个double类型的数组json对象 + * @brief 创建 double 数组节点 * - * @param numbers - * @param count - * @return RyanJson_t + * @param numbers 输入数组 + * @param count 元素个数 + * @return RyanJson_t 新建数组节点,失败返回 NULL */ RyanJson_t RyanJsonCreateDoubleArray(const double *numbers, uint32_t count) { RyanJsonCheckReturnNull(NULL != numbers); RyanJson_t pJson = RyanJsonCreateArray(); - for (uint32_t i = 0; pJson && i < count; i++) { RyanJsonAddDoubleToArray(pJson, numbers[i]); } + RyanJsonCheckReturnNull(NULL != pJson); + for (uint32_t i = 0; i < count; i++) + { + RyanJsonCheckCode(RyanJsonTrue == RyanJsonAddDoubleToArray(pJson, numbers[i]), { + RyanJsonDelete(pJson); + return NULL; + }); + } return pJson; } /** - * @brief 创建一个string类型的数组json对象 + * @brief 创建字符串数组节点 * - * @param strings - * @param count - * @return RyanJson_t + * @param strings 输入字符串数组 + * @param count 元素个数 + * @return RyanJson_t 新建数组节点,失败返回 NULL */ RyanJson_t RyanJsonCreateStringArray(const char **strings, uint32_t count) { RyanJsonCheckReturnNull(NULL != strings); RyanJson_t pJson = RyanJsonCreateArray(); - for (uint32_t i = 0; pJson && i < count; i++) { RyanJsonAddStringToArray(pJson, strings[i]); } - return pJson; -} - -/** - * @brief 递归比较两个 pJson 对象key是否相等。 - * 此接口效率较低, 谨慎使用 - * @param leftJson - * @param rightJson - * @return RyanJsonBool_e - */ -RyanJsonBool_e RyanJsonCompareOnlyKey(RyanJson_t leftJson, RyanJson_t rightJson) -{ - if (NULL == leftJson || NULL == rightJson) { return RyanJsonFalse; } - - // 相同的对象相等 - if (leftJson == rightJson) { return RyanJsonTrue; } - - if (RyanJsonGetType(leftJson) != RyanJsonGetType(rightJson)) { return RyanJsonFalse; } - - switch (RyanJsonGetType(leftJson)) + RyanJsonCheckReturnNull(NULL != pJson); + for (uint32_t i = 0; i < count; i++) { - case RyanJsonTypeBool: - case RyanJsonTypeNull: - case RyanJsonTypeNumber: - case RyanJsonTypeString: return RyanJsonTrue; - - case RyanJsonTypeArray: { - if (RyanJsonGetSize(leftJson) != RyanJsonGetSize(rightJson)) { return RyanJsonFalse; } - - RyanJson_t item; - uint32_t itemIndex = 0; - RyanJsonArrayForEach(leftJson, item) - { - if (RyanJsonTrue != RyanJsonCompareOnlyKey(item, RyanJsonGetObjectByIndex(rightJson, itemIndex))) - { - return RyanJsonFalse; - } - itemIndex++; - } - return RyanJsonTrue; + RyanJsonCheckCode(RyanJsonTrue == RyanJsonAddStringToArray(pJson, strings[i]), { + RyanJsonDelete(pJson); + return NULL; + }); } - - case RyanJsonTypeObject: { - if (RyanJsonGetSize(leftJson) != RyanJsonGetSize(rightJson)) { return RyanJsonFalse; } - - RyanJson_t item; - RyanJsonObjectForEach(leftJson, item) - { - if (RyanJsonTrue != RyanJsonCompareOnlyKey(item, RyanJsonGetObjectByKey(rightJson, RyanJsonGetKey(item)))) - { - return RyanJsonFalse; - } - } - return RyanJsonTrue; - } - } - - return RyanJsonFalse; + return pJson; } diff --git a/RyanJson/RyanJsonUtils.h b/RyanJson/RyanJsonUtils.h deleted file mode 100644 index 1891f10..0000000 --- a/RyanJson/RyanJsonUtils.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef __RyanJsonUtils__ -#define __RyanJsonUtils__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "RyanJson.h" -#include - -// 语法糖,根据传入的numbers数组创建一个int类型的数组。如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateIntArray(const int32_t *numbers, uint32_t count); -// 语法糖,根据传入的numbers数组创建一个double类型的数组。如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateDoubleArray(const double *numbers, uint32_t count); -// 语法糖,根据传入的strings数组创建一个string类型的数组。如果没有添加到父json, 则需释放内存 -extern RyanJson_t RyanJsonCreateStringArray(const char **strings, uint32_t count); - -extern RyanJsonBool_e RyanJsonCompareOnlyKey(RyanJson_t leftJson, RyanJson_t rightJson); - -/** - * @brief 查询函数,此接口较为底层,请使用下发的宏定义调用 - */ -extern RyanJson_t RyanJsonGetObjectByIndexs(RyanJson_t pJson, uint32_t index, ...); -extern RyanJson_t RyanJsonGetObjectByKeys(RyanJson_t pJson, const char *key, ...); - -/** - * @brief 可使用此宏进行嵌套式查找,例如 RyanJsonGetObjectToKey(json, "test", "inter") - * - */ -#define RyanJsonGetObjectToKey(pJson, key, ...) RyanJsonGetObjectByKeys(pJson, (key), ##__VA_ARGS__, NULL) - -/** - * @brief 可使用此宏进行嵌套式查找,例如 RyanJsonGetObjectToIndex(json, 0, 2) - * - */ -#define RyanJsonGetObjectToIndex(pJson, index, ...) RyanJsonGetObjectByIndexs(pJson, (index), ##__VA_ARGS__, UINT32_MAX) - -#define RyanJsonHasObjectToKey(pJson, key, ...) RyanJsonMakeBool(RyanJsonGetObjectByKeys(pJson, key, ##__VA_ARGS__, NULL)) -#define RyanJsonHasObjectToIndex(pJson, index, ...) RyanJsonMakeBool(RyanJsonGetObjectByIndexs(pJson, index, ##__VA_ARGS__, UINT32_MAX)) - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/example/RyanJsonExample.c b/example/RyanJsonExample.c index cfc7667..0f25ad1 100644 --- a/example/RyanJsonExample.c +++ b/example/RyanJsonExample.c @@ -1,25 +1,16 @@ - -#include -#include -#include -#include -#include - #include "RyanJson.h" -#include "RyanJsonUtils.h" -#include "valloc.h" /** - * @brief 生成json示例 + * @brief Json 构建示例 * - * @return int + * @return RyanJsonBool_e */ -static int createJsonExample(void) +static RyanJsonBool_e createJsonExample(void) { char *str = NULL; RyanJson_t jsonRoot, item; - // 对象生成测试 + // 构建根对象 jsonRoot = RyanJsonCreateObject(); RyanJsonAddIntToObject(jsonRoot, "inter", 16); RyanJsonAddDoubleToObject(jsonRoot, "double", 16.89); @@ -36,10 +27,10 @@ static int createJsonExample(void) RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); RyanJsonAddNullToObject(item, "null"); - RyanJsonAddItemToObject(jsonRoot, "item", item); // 将上面创建的item子对象添加到root父对象 + RyanJsonAddItemToObject(jsonRoot, "item", item); // 把子对象挂到根对象 // 添加数字子数组 - int arrayInt[] = {16, 16, 16, 16, 16}; + int32_t arrayInt[] = {16, 16, 16, 16, 16}; RyanJsonAddItemToObject(jsonRoot, "arrayInt", RyanJsonCreateIntArray(arrayInt, sizeof(arrayInt) / sizeof(arrayInt[0]))); // 添加浮点数子数组 @@ -60,7 +51,7 @@ static int createJsonExample(void) RyanJsonAddBoolToArray(array, RyanJsonTrue); RyanJsonAddBoolToArray(array, RyanJsonFalse); RyanJsonAddNullToArray(array); - RyanJsonAddItemToObject(jsonRoot, "array", array); // 将上面创建的item子对象数组添加到root父对象 + RyanJsonAddItemToObject(jsonRoot, "array", array); // 把混合数组挂到根对象 // 添加对象数组 RyanJson_t arrayItem = RyanJsonCreateArray(); @@ -71,7 +62,7 @@ static int createJsonExample(void) RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); RyanJsonAddNullToObject(item, "null"); - RyanJsonAddItemToObject(arrayItem, "item", item); // 将item对象添加到arrayItem数组里面 + RyanJsonAddItemToObject(arrayItem, "item", item); // 将对象加入数组 item = RyanJsonCreateObject(); RyanJsonAddIntToObject(item, "inter", 16); @@ -80,122 +71,206 @@ static int createJsonExample(void) RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); RyanJsonAddNullToObject(item, "null"); - RyanJsonAddItemToObject(arrayItem, "item", item); // 将item对象添加到arrayItem数组里面 + RyanJsonAddItemToObject(arrayItem, "item", item); // 将对象加入数组 - RyanJsonAddItemToObject(jsonRoot, "arrayItem", arrayItem); // 将arrayItem数组添加到root父对象 + RyanJsonAddItemToObject(jsonRoot, "arrayItem", arrayItem); // 把对象数组挂到根对象 uint32_t len = 0; - str = RyanJsonPrint(jsonRoot, 250, RyanJsonTrue, &len); // 以带格式方式将数据打印出来 + str = RyanJsonPrint(jsonRoot, 250, RyanJsonTrue, &len); // 格式化打印 printf("strLen: %" PRIu32 ", data: %s\r\n", len, str); RyanJsonFree(str); RyanJsonDelete(jsonRoot); - - return 0; + return RyanJsonTrue; } /** - * @brief 序列化json文本示例 + * @brief Json 解析示例 * - * @return int + * @return RyanJsonBool_e */ -static int loadJsonExample(void) +static RyanJsonBool_e loadJsonExample(void) { char *str = NULL; - RyanJson_t jsonRoot; - const char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89," - "16.89,16.89,16.89,16.89],\"arrayString\":[\"hello\"," - "\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{" - "\"inter\":16,\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]}"; - - // 解析json数据 + RyanJson_t jsonRoot = NULL; + static const char *jsonstr = + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," + "\"item\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89," + "16.89,16.89,16.89,16.89],\"arrayString\":[\"hello\"," + "\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{" + "\"inter\":16,\"double\":16.89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]}"; + + // 解析 Json 文本 jsonRoot = RyanJsonParse(jsonstr); - if (jsonRoot == NULL) + if (NULL == jsonRoot) { - printf("%s:%d 序列化失败\r\n", __FILE__, __LINE__); - return -1; + printf("%s:%d 解析失败\r\n", __FILE__, __LINE__); + return RyanJsonFalse; } - // 将序列化的数据以无格式样式打印出来,并和原始数据进行对比 + // 读取 int32_t 数据 + int32_t inter = RyanJsonGetIntValue(RyanJsonGetObjectByKey(jsonRoot, "inter")); + if (16 != inter) + { + printf("%s:%d 读取int失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + } + + // 读取 double 数据 + double doubleValue = RyanJsonGetDoubleValue(RyanJsonGetObjectByKey(jsonRoot, "double")); + if (RyanJsonFalse == RyanJsonCompareDouble(doubleValue, 16.89)) + { + printf("%s:%d 读取double失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + } + + // 读取 string 数据 + const char *strValue = RyanJsonGetStringValue(RyanJsonGetObjectByKey(jsonRoot, "string")); + if (0 != strcmp(strValue, "hello")) + { + printf("%s:%d 读取string失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + } + + // 读取 bool 数据 + RyanJsonBool_e boolValue = RyanJsonGetBoolValue(RyanJsonGetObjectByKey(jsonRoot, "boolTrue")); + if (RyanJsonTrue != boolValue) + { + printf("%s:%d 读取bool失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + } + + // 读取 null 数据 + if (RyanJsonTrue != RyanJsonIsNull(RyanJsonGetObjectByKey(jsonRoot, "null"))) + { + printf("%s:%d 读取null失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + } + + // 将解析结果以无格式重新输出,并与原始文本对比 str = RyanJsonPrint(jsonRoot, 250, RyanJsonFalse, NULL); - if (strcmp(str, jsonstr) != 0) + if (0 != strcmp(str, jsonstr)) { - printf("%s:%d 序列化与反序列化后的数据不对应\r\n", __FILE__, __LINE__); + printf("%s:%d 解析后再序列化结果不一致 %s\r\n", __FILE__, __LINE__, str); RyanJsonFree(str); RyanJsonDelete(jsonRoot); - return -1; + return RyanJsonFalse; } RyanJsonFree(str); - // 将序列化的数据以有格式样式打印出来 + // 将解析结果以格式化样式打印 uint32_t len = 0; str = RyanJsonPrint(jsonRoot, 250, RyanJsonTrue, &len); printf("strLen: %" PRIu32 ", data: %s\r\n", len, str); RyanJsonFree(str); - // 删除json对象 + // 释放 Json 对象 RyanJsonDelete(jsonRoot); - return 0; + return RyanJsonTrue; } /** - * @brief 修改json示例 + * @brief Json 修改示例 * - * @return int + * @return RyanJsonBool_e */ -static int changeJsonExample(void) +static RyanJsonBool_e changeJsonExample(void) { char *str = NULL; RyanJson_t jsonRoot; - const char *jsonstr = "{\"name\":\"Mash\",\"star\":4,\"hits\":[2,2,1,3]}"; + const char *jsonstr = "{\"name\":\"Mash\",\"star\":4,\"doubleKey\":4.4,\"boolKey\":true,\"hits\":[2,2,1,3]}"; - // 解析json数据 + // 解析 Json 文本 jsonRoot = RyanJsonParse(jsonstr); if (jsonRoot == NULL) { - printf("%s:%d 序列化失败\r\n", __FILE__, __LINE__); - return -1; + printf("%s:%d 解析失败\r\n", __FILE__, __LINE__); + return RyanJsonFalse; + } + + // 修改 key + RyanJsonChangeKey(RyanJsonGetObjectByKey(jsonRoot, "name"), "name2"); + if (0 != strcmp("name2", RyanJsonGetKey(RyanJsonGetObjectByKey(jsonRoot, "name2")))) + { + printf("%s:%d 修改失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + } + + // 修改 strValue + RyanJsonChangeStringValue(RyanJsonGetObjectByKey(jsonRoot, "name2"), "Ryan"); + if (0 != strcmp("Ryan", RyanJsonGetStringValue(RyanJsonGetObjectByKey(jsonRoot, "name2")))) + { + printf("%s:%d 修改失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + } + + // 修改 intValue + RyanJsonChangeIntValue(RyanJsonGetObjectByKey(jsonRoot, "star"), 5); + if (5 != RyanJsonGetIntValue(RyanJsonGetObjectByKey(jsonRoot, "star"))) + { + printf("%s:%d 修改失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + } + + // 修改 doubleValue + RyanJsonChangeDoubleValue(RyanJsonGetObjectByKey(jsonRoot, "doubleKey"), 5.5); + if (RyanJsonFalse == RyanJsonCompareDouble(RyanJsonGetDoubleValue(RyanJsonGetObjectByKey(jsonRoot, "doubleKey")), 5.5)) + { + printf("%s:%d 修改失败\r\n", __FILE__, __LINE__); + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; } - RyanJsonChangeStringValue(RyanJsonGetObjectByKey(jsonRoot, "name"), "Ryan"); - if (0 != strcmp("Ryan", RyanJsonGetStringValue(RyanJsonGetObjectByKey(jsonRoot, "name")))) + // 修改 boolValue + RyanJsonChangeBoolValue(RyanJsonGetObjectByKey(jsonRoot, "boolKey"), RyanJsonFalse); + if (RyanJsonFalse != RyanJsonGetBoolValue(RyanJsonGetObjectByKey(jsonRoot, "boolKey"))) { printf("%s:%d 修改失败\r\n", __FILE__, __LINE__); RyanJsonDelete(jsonRoot); - return -1; + return RyanJsonFalse; } + // 替换节点(同时改变节点类型) RyanJsonReplaceByKey(jsonRoot, "star", RyanJsonCreateString("", "123456")); - // 将序列化的数据以有格式样式打印出来 + // 将修改结果以格式化样式打印 uint32_t len = 0; str = RyanJsonPrint(jsonRoot, 250, RyanJsonTrue, &len); printf("strLen: %" PRIu32 ", data: %s\r\n", len, str); RyanJsonFree(str); - // 删除json对象 + // 释放 Json 对象 RyanJsonDelete(jsonRoot); - return 0; + return RyanJsonTrue; } RyanJsonBool_e RyanJsonExample(void) { - RyanJsonInitHooks(v_malloc, v_free, v_realloc); + RyanJsonInitHooks(malloc, free, NULL); printf("\r\n--------------------------- RyanJson 生成示例 --------------------------\r\n"); - createJsonExample(); + RyanJsonCheckReturnFalse(RyanJsonTrue == createJsonExample()); - printf("\r\n--------------------------- RyanJson 序列化json文本示例 --------------------------\r\n"); - loadJsonExample(); + printf("\r\n--------------------------- RyanJson 解析json文本示例 --------------------------\r\n"); + RyanJsonCheckReturnFalse(RyanJsonTrue == loadJsonExample()); printf("\r\n--------------------------- RyanJson 修改json示例 --------------------------\r\n"); - changeJsonExample(); + RyanJsonCheckReturnFalse(RyanJsonTrue == changeJsonExample()); + + // 更多功能请查看 RyanJson.h 与 test 目录下的测试用例 return RyanJsonTrue; } diff --git a/run_coverage.sh b/run_coverage.sh deleted file mode 100755 index b003e29..0000000 --- a/run_coverage.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -set -e # 遇到错误立即退出 - -# ================================ -# 1. 运行 fuzzer -# ================================ -./build/linux/x86/release/RyanJson \ - ./test/fuzzer/corpus \ - -dict=./test/fuzzer/RyanJsonFuzzer.dict \ - -timeout=4 \ - -runs=99999999 \ - -verbosity=0 \ - -max_len=8192 \ - -workers=5 \ - -jobs=10 - - -# ================================ -# 2. 合并 profile 数据 -# ================================ -llvm-profdata merge -sparse default.profraw -o default.profdata - -# ================================ -# 3. 生成覆盖率报告(文本汇总) -# ================================ -# 注意:llvm-cov report 只支持汇总统计,不支持行级参数 -# --show-functions 显示函数级覆盖率 -# --show-region-summary 显示区域覆盖率 -llvm-cov report ./build/linux/x86/release/RyanJson \ - -instr-profile=default.profdata \ - -show-mcdc-summary \ - -show-functions \ - -show-region-summary \ - -sources ./test/fuzzer ./RyanJson - -# ================================ -# 4. 生成覆盖率报告(HTML详细) -# ================================ -llvm-cov show ./build/linux/x86/release/RyanJson \ - -instr-profile=default.profdata \ - -format=html \ - -output-dir=coverage/docs \ - -show-mcdc-summary \ - -show-expansions \ - -show-regions \ - -show-line-counts-or-regions \ - -sources ./RyanJson -# -sources ./test/fuzzer ./RyanJson diff --git a/run_local_base.sh b/run_local_base.sh new file mode 100755 index 0000000..4bdd794 --- /dev/null +++ b/run_local_base.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -euo pipefail + +# 本地一键 Base(单元测试矩阵)。 +# 用途:直接调用 scripts/ci/runBaseCoverage.sh,免去手工拼参数。 +# 默认值: +# UNIT_MODE=full(8 组配置全覆盖) +# UNIT_SKIP_COV=1(跳过覆盖率,提速) +# UNIT_STOP_ON_FAIL=1(首个失败立即退出) +# XMAKE_FORCE_CLEAN=0(增量配置,减少重编译) +# 以上参数都可用同名环境变量临时覆盖。 + +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${scriptDir}" + +unitMode="${UNIT_MODE:-full}" +unitSkipCov="${UNIT_SKIP_COV:-1}" +unitStopOnFail="${UNIT_STOP_ON_FAIL:-1}" +xmakeForceClean="${XMAKE_FORCE_CLEAN:-0}" + +echo "====================================================" +echo "本地 Base 启动(单元测试矩阵)" +echo " - UNIT_MODE=${unitMode}" +echo " - UNIT_SKIP_COV=${unitSkipCov}" +echo " - UNIT_STOP_ON_FAIL=${unitStopOnFail}" +echo " - XMAKE_FORCE_CLEAN=${xmakeForceClean}" +echo "====================================================" + +UNIT_MODE="${unitMode}" \ +UNIT_SKIP_COV="${unitSkipCov}" \ +UNIT_STOP_ON_FAIL="${unitStopOnFail}" \ +XMAKE_FORCE_CLEAN="${xmakeForceClean}" \ +bash ./scripts/ci/runBaseCoverage.sh diff --git a/run_local_ci.sh b/run_local_ci.sh new file mode 100755 index 0000000..5b9c47b --- /dev/null +++ b/run_local_ci.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +# 本地一键 CI(模拟 ci-pr)。 +# 执行顺序: +# 先跑 full 单元矩阵(8 组),再跑 quick fuzz(1 组默认语义)。 +# 默认参数与 ci-pr.yml 对齐: +# unit: UNIT_MODE=full, UNIT_SKIP_COV=1 +# fuzz: FUZZ_MODE=quick, FUZZ_SKIP_COV=1, FUZZ_MAX_TOTAL_TIME=45, workers/jobs=2 +# fuzz 阶段参数可用同名环境变量临时覆盖。 + +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${scriptDir}" + +echo "====================================================" +echo "本地 CI 启动:阶段 1/2 -> Base 单元测试" +echo "====================================================" +bash ./run_local_base.sh + +echo "====================================================" +echo "本地 CI 启动:阶段 2/2 -> Fuzz quick" +echo "====================================================" + +RYANJSON_STRICT_OBJECT_KEY_CHECK="${RYANJSON_STRICT_OBJECT_KEY_CHECK:-false}" \ +RYANJSON_DEFAULT_ADD_AT_HEAD="${RYANJSON_DEFAULT_ADD_AT_HEAD:-true}" \ +RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC="${RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC:-true}" \ +FUZZ_MODE="${FUZZ_MODE:-quick}" \ +FUZZ_SKIP_COV="${FUZZ_SKIP_COV:-1}" \ +FUZZ_MAX_TOTAL_TIME="${FUZZ_MAX_TOTAL_TIME:-45}" \ +FUZZ_WORKERS="${FUZZ_WORKERS:-2}" \ +FUZZ_JOBS="${FUZZ_JOBS:-2}" \ +XMAKE_FORCE_CLEAN="${XMAKE_FORCE_CLEAN:-0}" \ +bash ./scripts/ci/runCoverage.sh + +echo "====================================================" +echo "本地 CI 执行完成" +echo "====================================================" diff --git a/run_local_format.sh b/run_local_format.sh new file mode 100755 index 0000000..c769f23 --- /dev/null +++ b/run_local_format.sh @@ -0,0 +1,89 @@ +#!/bin/bash +set -euo pipefail + +# 本地一键 clang-format。 +# 默认行为:格式化仓库内受管源码(C/C++ 头源文件)。 +# 可选参数: +# --check 仅检查,不修改文件(不符合时返回非 0) +# --changed 仅处理当前改动文件(默认处理全部受管源码) + +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${scriptDir}" + +mode="write" # write | check +scope="all" # all | changed + +while (($# > 0)); do + case "$1" in + --check) + mode="check" + ;; + --changed) + scope="changed" + ;; + -h | --help) + echo "用法: bash ./run_local_format.sh [--check] [--changed]" + exit 0 + ;; + *) + echo "[错误] 未知参数: $1" + echo "用法: bash ./run_local_format.sh [--check] [--changed]" + exit 2 + ;; + esac + shift +done + +if command -v clang-format >/dev/null 2>&1; then + formatter="clang-format" +elif command -v clang-format-21 >/dev/null 2>&1; then + formatter="clang-format-21" +elif command -v clang-format-20 >/dev/null 2>&1; then + formatter="clang-format-20" +else + echo "[错误] 未找到 clang-format,可执行文件名尝试过: clang-format / clang-format-21 / clang-format-20" + exit 127 +fi + +allFiles=() +if [[ "${scope}" == "all" ]]; then + mapfile -d '' allFiles < <(git ls-files -z -- '*.c' '*.h' '*.cc' '*.cpp' '*.hpp') +else + mapfile -d '' allFiles < <(git diff --name-only -z -- '*.c' '*.h' '*.cc' '*.cpp' '*.hpp') +fi + +files=() +for f in "${allFiles[@]}"; do + case "${f}" in + test/externalModule/* | build/* | coverage/* | .xmake/* | test/fuzzer/corpus/*) ;; + *) + files+=("${f}") + ;; + esac +done + +echo "====================================================" +echo "本地格式化启动" +echo " - formatter=${formatter}" +echo " - mode=${mode}" +echo " - scope=${scope}" +echo " - files=${#files[@]}" +echo "====================================================" + +if [[ ${#files[@]} -eq 0 ]]; then + echo "[信息] 没有可处理的源码文件" + exit 0 +fi + +if [[ "${mode}" == "check" ]]; then + if printf '%s\0' "${files[@]}" | xargs -0 "${formatter}" --dry-run --Werror; then + echo "[完成] clang-format 检查通过" + else + echo "[失败] 存在不符合 .clang-format 的文件,请执行: bash ./run_local_format.sh" + exit 1 + fi +else + printf '%s\0' "${files[@]}" | xargs -0 "${formatter}" -i + echo "[完成] clang-format 已应用到 ${#files[@]} 个文件" +fi + diff --git a/run_local_fuzz.sh b/run_local_fuzz.sh new file mode 100755 index 0000000..33735f2 --- /dev/null +++ b/run_local_fuzz.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -euo pipefail + +# 本地一键 Fuzz(默认低内存并发,可通过环境变量覆盖)。 +# 用途:封装常用 fuzz 参数,避免每次手工传 FUZZ_WORKERS/FUZZ_JOBS/FUZZ_RSS_LIMIT_MB。 +# 默认行为: +# workers/jobs 默认 2/2(可覆盖) +# 默认 rss_limit_mb=2048(可覆盖) +# 按轮次执行(默认 FUZZ_RUNS=10000000,不走 max_total_time) +# 默认模式 nightly,默认生成覆盖率 +# 可覆盖参数: +# FUZZ_MODE/FUZZ_SKIP_COV/FUZZ_RUNS/FUZZ_TIMEOUT/FUZZ_MAX_LEN +# FUZZ_WORKERS/FUZZ_JOBS/FUZZ_RSS_LIMIT_MB/FUZZ_MALLOC_LIMIT_MB +# 三个语义宏默认值(可覆盖): +# RYANJSON_STRICT_OBJECT_KEY_CHECK=false +# RYANJSON_DEFAULT_ADD_AT_HEAD=false +# RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC=false + +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${scriptDir}" + +fuzzMode="${FUZZ_MODE:-nightly}" +fuzzSkipCov="${FUZZ_SKIP_COV:-0}" +fuzzRuns="${FUZZ_RUNS:-10000000}" +fuzzTimeout="${FUZZ_TIMEOUT:-4}" +fuzzMaxLen="${FUZZ_MAX_LEN:-8192}" +fuzzWorkers="${FUZZ_WORKERS:-3}" +fuzzJobs="${FUZZ_JOBS:-9}" +fuzzRssLimitMb="${FUZZ_RSS_LIMIT_MB:-4096}" +fuzzMallocLimitMb="${FUZZ_MALLOC_LIMIT_MB:-}" + +export RYANJSON_STRICT_OBJECT_KEY_CHECK="${RYANJSON_STRICT_OBJECT_KEY_CHECK:-false}" +export RYANJSON_DEFAULT_ADD_AT_HEAD="${RYANJSON_DEFAULT_ADD_AT_HEAD:-false}" +export RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC="${RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC:-false}" + +echo "====================================================" +echo "本地 Fuzz 启动" +echo " - FUZZ_MODE=${fuzzMode}" +echo " - FUZZ_SKIP_COV=${fuzzSkipCov}" +echo " - FUZZ_RUNS=${fuzzRuns}" +echo " - FUZZ_TIMEOUT=${fuzzTimeout}" +echo " - FUZZ_MAX_LEN=${fuzzMaxLen}" +echo " - FUZZ_WORKERS=${fuzzWorkers}" +echo " - FUZZ_JOBS=${fuzzJobs}" +if [[ -n "${fuzzRssLimitMb}" ]]; then + echo " - FUZZ_RSS_LIMIT_MB=${fuzzRssLimitMb}" +fi +if [[ -n "${fuzzMallocLimitMb}" ]]; then + echo " - FUZZ_MALLOC_LIMIT_MB=${fuzzMallocLimitMb}" +fi +echo " - RYANJSON_STRICT_OBJECT_KEY_CHECK=${RYANJSON_STRICT_OBJECT_KEY_CHECK}" +echo " - RYANJSON_DEFAULT_ADD_AT_HEAD=${RYANJSON_DEFAULT_ADD_AT_HEAD}" +echo " - RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC=${RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC}" +echo "====================================================" + +FUZZ_MODE="${fuzzMode}" \ +FUZZ_SKIP_COV="${fuzzSkipCov}" \ +FUZZ_RUNS="${fuzzRuns}" \ +FUZZ_TIMEOUT="${fuzzTimeout}" \ +FUZZ_MAX_LEN="${fuzzMaxLen}" \ +FUZZ_WORKERS="${fuzzWorkers}" \ +FUZZ_JOBS="${fuzzJobs}" \ +FUZZ_RSS_LIMIT_MB="${fuzzRssLimitMb}" \ +FUZZ_MALLOC_LIMIT_MB="${fuzzMallocLimitMb}" \ +bash ./scripts/ci/runCoverage.sh diff --git a/run_local_qemu.sh b/run_local_qemu.sh new file mode 100755 index 0000000..201c4e0 --- /dev/null +++ b/run_local_qemu.sh @@ -0,0 +1,440 @@ +#!/usr/bin/env bash +set -euo pipefail + +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${scriptDir}" + +userMachineSet=0 +userCpuSet=0 +userTargetSet=0 +if [[ -n "${QEMU_MACHINE+x}" ]]; then userMachineSet=1; fi +if [[ -n "${QEMU_CPU+x}" ]]; then userCpuSet=1; fi +if [[ -n "${QEMU_TARGET+x}" ]]; then userTargetSet=1; fi + +qemuMode="${QEMU_MODE:-full}" +qemuMachine="${QEMU_MACHINE:-mps2-an386}" +qemuCpu="${QEMU_CPU:-cortex-m4}" +qemuTimeoutSec="${QEMU_TIMEOUT_SEC:-120}" +qemuTarget="${QEMU_TARGET:-RyanJsonQemu}" +qemuForceClean="${QEMU_FORCE_CONFIG_CLEAN:-0}" +qemuStopOnFail="${QEMU_STOP_ON_FAIL:-1}" +qemuLogRoot="${QEMU_LOG_ROOT:-coverage/qemu}" +qemuMemory="${QEMU_MEMORY:-64M}" +qemuConsoleLog="${QEMU_CONSOLE_LOG:-1}" +qemuSaveLog="${QEMU_SAVE_LOG:-0}" +qemuMaxCases="${QEMU_MAX_CASES:-0}" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "[错误] 缺少命令: $1" + echo "[提示] 可执行: bash ./scripts/setup/install_qemu_deps.sh" + exit 1 + fi +} + +require_cmd xmake +require_cmd arm-none-eabi-gcc +require_cmd arm-none-eabi-objcopy +require_cmd qemu-system-arm + +qemuSemihostingMode="legacy" +if qemu-system-arm -help 2>&1 | grep -q -- "-semihosting-config"; then + qemuSemihostingMode="config" +fi + +machine_supported() { + local machineName="$1" + qemu-system-arm -machine help | awk '{print $1}' | grep -Fxq "${machineName}" +} + +cleanQemuStream() { + if command -v perl >/dev/null 2>&1; then + # Normalize UART CRLF to LF so grep ^...$ marker checks are reliable. + # Keep ANSI ESC (\x1B) for colored test output; strip other non-printable noise bytes. + perl -ne 's/\r//g; s/[\x00-\x08\x0B\x0C\x0E-\x1A\x1C-\x1F\x7F]//g; next if length($_) > 4096; print;' + else + tr -d '\000\r' + fi +} + +getQemuChildPid() { + local parentPid="$1" + pgrep -P "${parentPid}" qemu-system-arm | head -n 1 || true +} + +stopQemuRun() { + local parentPid="$1" + local reason="$2" + local childPid="" + local waitDeadline=0 + + childPid="$(getQemuChildPid "${parentPid}")" + if [[ -n "${childPid}" ]]; then + kill -TERM "${childPid}" >/dev/null 2>&1 || true + fi + + waitDeadline=$((SECONDS + 3)) + while kill -0 "${parentPid}" >/dev/null 2>&1; do + if ((SECONDS >= waitDeadline)); then + break + fi + sleep 0.2 + done + + if kill -0 "${parentPid}" >/dev/null 2>&1; then + kill -TERM "${parentPid}" >/dev/null 2>&1 || true + sleep 1 + kill -KILL "${parentPid}" >/dev/null 2>&1 || true + fi +} + +# 优先 mps2-an386(CM4F),若当前 QEMU 不支持则自动回退到 mps2-an385(CM3)。 +if ! machine_supported "${qemuMachine}"; then + if [[ "${qemuMachine}" == "mps2-an386" ]] && machine_supported "mps2-an385"; then + echo "[信息] 当前 QEMU 不支持 mps2-an386,自动回退到 mps2-an385。" + qemuMachine="mps2-an385" + if [[ "${userCpuSet}" -eq 0 ]]; then + qemuCpu="cortex-m3" + fi + if [[ "${userTargetSet}" -eq 0 ]]; then + qemuTarget="RyanJsonQemuCm3" + fi + else + echo "[错误] QEMU 不支持 machine=${qemuMachine}" + echo "[提示] 可用 machine 列表:" + qemu-system-arm -machine help | sed -n '1,120p' + exit 1 + fi +fi + +# 用户直接选择 an385 且未显式指定 target/cpu 时,自动切到 CM3 构建目标。 +if [[ "${qemuMachine}" == "mps2-an385" ]]; then + if [[ "${userCpuSet}" -eq 0 ]]; then + qemuCpu="cortex-m3" + fi + if [[ "${userTargetSet}" -eq 0 ]]; then + qemuTarget="RyanJsonQemuCm3" + fi +fi + +caseList=() +add_case() { + caseList+=("$1 $2 $3") +} + +case "${qemuMode}" in + quick) + add_case false true true + add_case true false true + ;; + nightly) + for strictKey in false true; do + for addAtHead in false true; do + add_case "${strictKey}" "${addAtHead}" true + done + done + ;; + full) + for strictKey in false true; do + for addAtHead in false true; do + for scientific in false true; do + add_case "${strictKey}" "${addAtHead}" "${scientific}" + done + done + done + ;; + *) + echo "[错误] QEMU_MODE 仅支持 quick/nightly/full,当前值:${qemuMode}" + exit 1 + ;; +esac + +if ! [[ "${qemuMaxCases}" =~ ^[0-9]+$ ]]; then + echo "[错误] QEMU_MAX_CASES 仅支持非负整数,当前值:${qemuMaxCases}" + exit 1 +fi + +if ! [[ "${qemuSaveLog}" =~ ^[01]$ ]]; then + echo "[错误] QEMU_SAVE_LOG 仅支持 0/1,当前值:${qemuSaveLog}" + exit 1 +fi + +if ! [[ "${qemuConsoleLog}" =~ ^[01]$ ]]; then + echo "[错误] QEMU_CONSOLE_LOG 仅支持 0/1,当前值:${qemuConsoleLog}" + exit 1 +fi + +if [[ "${qemuConsoleLog}" == "0" && "${qemuSaveLog}" == "0" ]]; then + echo "[信息] QEMU_CONSOLE_LOG=0 且 QEMU_SAVE_LOG=0 无可见输出,自动切换 QEMU_CONSOLE_LOG=1。" + qemuConsoleLog="1" +fi + +if [[ "${qemuSaveLog}" == "1" ]]; then + mkdir -p "${qemuLogRoot}" +fi + +if (( qemuMaxCases > 0 )) && (( qemuMaxCases < ${#caseList[@]} )); then + caseList=("${caseList[@]:0:qemuMaxCases}") +fi + +echo "====================================================" +echo "QEMU 本地链路启动(完整 localbase 单测 + 对齐语义)" +echo " - MODE=${qemuMode}" +echo " - TARGET=${qemuTarget}" +echo " - MACHINE=${qemuMachine}" +echo " - CPU=${qemuCpu}" +echo " - TIMEOUT=${qemuTimeoutSec}s" +echo " - LOG_ROOT=${qemuLogRoot}" +echo " - MEMORY=${qemuMemory}" +echo " - CONSOLE_LOG=${qemuConsoleLog}" +echo " - SAVE_LOG=${qemuSaveLog}" +echo " - SEMIHOSTING=${qemuSemihostingMode}" +echo " - MAX_CASES=${qemuMaxCases}" +echo "====================================================" + +totalCases="${#caseList[@]}" +caseIndex=0 +failedCases=0 + +logHasRequiredMarkers() { + local logPath="$1" + if ! grep -Eq "^\\[QEMU\\]\\[RESULT\\] UNIT_PASS code=0 tick=[0-9]+\r?$" "${logPath}"; then + return 1 + fi + + if ! grep -Eq "^\\[QEMU\\]\\[ALIGN\\] aligned_access PASS read=0x[0-9A-Fa-f]+\r?$" "${logPath}"; then + return 1 + fi + + if ! grep -Eq "^\\[QEMU\\]\\[ALIGN\\] unaligned_access TRIGGER addr=0x[0-9A-Fa-f]+\r?$" "${logPath}"; then + return 1 + fi + + if ! grep -Eq "^\\[QEMU\\]\\[RESULT\\] EXPECTED_UNALIGNED_FAULT (cfsr|fallbackAddr)=0x[0-9A-Fa-f]+\r?$" "${logPath}"; then + return 1 + fi + + if ! grep -Eq "^\\[QEMU\\]\\[HARDFAULT\\] CFSR=0x[0-9A-Fa-f]+ HFSR=0x[0-9A-Fa-f]+ BFAR=0x[0-9A-Fa-f]+ MMFAR=0x[0-9A-Fa-f]+\r?$" "${logPath}" \ + && ! grep -Eq "^\\[QEMU\\]\\[HARDFAULT\\] fallback_soft_trap_no_hw_fault addr=0x[0-9A-Fa-f]+\r?$" "${logPath}"; then + return 1 + fi + + return 0 +} + +logHasFailureMarkers() { + local logPath="$1" + if grep -Eq "^\\[QEMU\\]\\[RESULT\\] UNIT_FAIL code=-?[0-9]+\r?$" "${logPath}"; then + return 0 + fi + return 1 +} + +cleanupCaseLogIfNeeded() { + local keepCaseLog="$1" + local caseLogPath="$2" + if [[ "${keepCaseLog}" == "0" && -n "${caseLogPath}" ]]; then + rm -f "${caseLogPath}" >/dev/null 2>&1 || true + fi +} + +run_case() { + local strictKey="$1" + local addAtHead="$2" + local scientific="$3" + local caseName="strict_${strictKey}__head_${addAtHead}__sci_${scientific}" + local caseLogPath="" + local buildLogPath="" + local keepCaseLog="0" + local deadlineSec=0 + local qemuPid=0 + local qemuRc=0 + + echo "----------------------------------------------------" + echo "[用例] ${caseName}" + echo " - RyanJsonStrictObjectKeyCheck=${strictKey}" + echo " - RyanJsonDefaultAddAtHead=${addAtHead}" + echo " - RyanJsonSnprintfSupportScientific=${scientific}" + echo "----------------------------------------------------" + + export RYANJSON_STRICT_OBJECT_KEY_CHECK="${strictKey}" + export RYANJSON_DEFAULT_ADD_AT_HEAD="${addAtHead}" + export RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC="${scientific}" + + if [[ "${qemuForceClean}" == "1" ]]; then + xmake f -c -p cross -a arm + else + xmake f -p cross -a arm + fi + # 清理旧产物,避免构建失败时误用历史 ELF。 + find ./build -type f -name "${qemuTarget}.elf" -delete >/dev/null 2>&1 || true + buildLogPath="$(mktemp "/tmp/${caseName}.build.XXXX.log")" + if ! xmake -b "${qemuTarget}" 2>&1 | tee "${buildLogPath}"; then + echo "[错误] xmake 构建失败,已终止本用例并跳过 QEMU 运行。" + tail -n 120 "${buildLogPath}" || true + rm -f "${buildLogPath}" >/dev/null 2>&1 || true + return 1 + fi + if grep -Eq '(^|[^[:alpha:]])error:' "${buildLogPath}"; then + echo "[错误] 构建日志检测到编译错误,已终止本用例并跳过 QEMU 运行。" + tail -n 120 "${buildLogPath}" || true + rm -f "${buildLogPath}" >/dev/null 2>&1 || true + return 1 + fi + rm -f "${buildLogPath}" >/dev/null 2>&1 || true + + local elfPath + elfPath="$(find ./build -type f -name "${qemuTarget}.elf" | head -n 1 || true)" + if [[ -z "${elfPath}" ]]; then + echo "[错误] 未找到 ELF 输出(${qemuTarget}.elf)" + cleanupCaseLogIfNeeded "${keepCaseLog}" "${caseLogPath}" + return 1 + fi + + if [[ "${qemuSaveLog}" == "1" ]]; then + caseLogPath="${qemuLogRoot}/${caseName}.log" + keepCaseLog="1" + else + caseLogPath="$(mktemp "/tmp/${caseName}.XXXX.log")" + keepCaseLog="0" + fi + + echo "[信息] ELF: ${elfPath}" + if [[ "${qemuSaveLog}" == "1" ]]; then + echo "[阶段] 启动 QEMU 并抓取日志 -> ${caseLogPath}" + else + echo "[阶段] 启动 QEMU(终端实时输出,日志不落盘)" + fi + + local -a qemuArgs=( + -M "${qemuMachine}" + -cpu "${qemuCpu}" + -nographic + -kernel "${elfPath}" + ) + if [[ "${qemuSemihostingMode}" == "config" ]]; then + qemuArgs+=(-semihosting-config enable=on,target=native) + else + qemuArgs+=(-semihosting) + fi + if [[ -n "${qemuMemory}" ]]; then + qemuArgs+=(-m "${qemuMemory}") + fi + + : > "${caseLogPath}" + deadlineSec=$((SECONDS + qemuTimeoutSec)) + + set +e + if [[ "${qemuConsoleLog}" == "1" ]]; then + ( + qemu-system-arm "${qemuArgs[@]}" 2>&1 | cleanQemuStream | tee -a "${caseLogPath}" + ) & + else + ( + qemu-system-arm "${qemuArgs[@]}" 2>&1 | cleanQemuStream >> "${caseLogPath}" + ) & + fi + qemuPid=$! + + while true; do + if ! kill -0 "${qemuPid}" >/dev/null 2>&1; then + wait "${qemuPid}" + qemuRc=$? + break + fi + + if ((SECONDS >= deadlineSec)); then + stopQemuRun "${qemuPid}" "timeout" + wait "${qemuPid}" + qemuRc=124 + break + fi + + sleep 1 + done + set -e + + # Give the log pipeline a tiny grace window to flush trailing bytes. + sleep 0.1 + + if [[ "${qemuRc}" -eq 124 ]]; then + echo "[错误] QEMU 超时(${qemuTimeoutSec}s)" + tail -n 120 "${caseLogPath}" + cleanupCaseLogIfNeeded "${keepCaseLog}" "${caseLogPath}" + return 1 + fi + + if logHasFailureMarkers "${caseLogPath}"; then + echo "[错误] 用例失败(检测到 [QEMU][RESULT] UNIT_FAIL)" + tail -n 120 "${caseLogPath}" + cleanupCaseLogIfNeeded "${keepCaseLog}" "${caseLogPath}" + return 1 + fi + + local missing=0 + if ! logHasRequiredMarkers "${caseLogPath}"; then + if ! grep -Eq "^\\[QEMU\\]\\[RESULT\\] UNIT_PASS code=0 tick=[0-9]+\r?$" "${caseLogPath}"; then + echo "[错误] 日志缺少关键标记: [QEMU][RESULT] UNIT_PASS code=0 tick=" + fi + if ! grep -Eq "^\\[QEMU\\]\\[ALIGN\\] aligned_access PASS read=0x[0-9A-Fa-f]+\r?$" "${caseLogPath}"; then + echo "[错误] 日志缺少关键标记: [QEMU][ALIGN] aligned_access PASS read=0x" + fi + if ! grep -Eq "^\\[QEMU\\]\\[ALIGN\\] unaligned_access TRIGGER addr=0x[0-9A-Fa-f]+\r?$" "${caseLogPath}"; then + echo "[错误] 日志缺少关键标记: [QEMU][ALIGN] unaligned_access TRIGGER addr=0x" + fi + if ! grep -Eq "^\\[QEMU\\]\\[RESULT\\] EXPECTED_UNALIGNED_FAULT (cfsr|fallbackAddr)=0x[0-9A-Fa-f]+\r?$" "${caseLogPath}"; then + echo "[错误] 日志缺少关键标记: [QEMU][RESULT] EXPECTED_UNALIGNED_FAULT " + fi + if ! grep -Eq "^\\[QEMU\\]\\[HARDFAULT\\] CFSR=0x[0-9A-Fa-f]+ HFSR=0x[0-9A-Fa-f]+ BFAR=0x[0-9A-Fa-f]+ MMFAR=0x[0-9A-Fa-f]+\r?$" "${caseLogPath}" \ + && ! grep -Eq "^\\[QEMU\\]\\[HARDFAULT\\] fallback_soft_trap_no_hw_fault addr=0x[0-9A-Fa-f]+\r?$" "${caseLogPath}"; then + echo "[错误] 日志缺少 fault 现场标记(HARDFAULT cfsr/hfsr 或 fallback addr)" + fi + missing=1 + fi + + if [[ "${missing}" -ne 0 ]]; then + echo "[错误] 用例失败,日志尾部:" + tail -n 120 "${caseLogPath}" + cleanupCaseLogIfNeeded "${keepCaseLog}" "${caseLogPath}" + return 1 + fi + + cleanupCaseLogIfNeeded "${keepCaseLog}" "${caseLogPath}" + echo "[通过] ${caseName}" + return 0 +} + +for entry in "${caseList[@]}"; do + caseIndex=$((caseIndex + 1)) + read -r strictKey addAtHead scientific <<< "${entry}" + + echo + echo "====================================================" + echo "【QEMU 用例 ${caseIndex}/${totalCases}】" + echo "====================================================" + + if run_case "${strictKey}" "${addAtHead}" "${scientific}"; then + : + else + failedCases=$((failedCases + 1)) + if [[ "${qemuStopOnFail}" == "1" ]]; then + echo "[错误] 按 QEMU_STOP_ON_FAIL=1 提前终止。" + exit 1 + fi + fi +done + +echo +echo "QEMU 单测矩阵执行完成。" +echo " - 模式: ${qemuMode}" +echo " - 总用例: ${totalCases}" +echo " - 失败用例: ${failedCases}" +if [[ "${qemuSaveLog}" == "1" ]]; then + echo " - 日志目录: ${qemuLogRoot}" +else + echo " - 日志输出: 终端实时输出(不落盘)" +fi + +if [[ "${failedCases}" -gt 0 ]]; then + exit 1 +fi diff --git a/run_local_skills.sh b/run_local_skills.sh new file mode 100755 index 0000000..94d7314 --- /dev/null +++ b/run_local_skills.sh @@ -0,0 +1,143 @@ +#!/bin/bash +set -euo pipefail + +# 本地一键 skills 同步与规范校验。 +# 默认行为: +# 1) 同步各技能的 references/terminology.md 到统一占位模板 +# 2) 使用 skill-creator 的 quick_validate.py 校验每个技能 +# 3) 校验 agents/openai.yaml 关键字段与默认 prompt 的技能名引用 +# +# 可选参数: +# --sync-only 仅执行同步 +# --validate-only 仅执行校验 + +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${scriptDir}" + +doSync=1 +doValidate=1 + +while (($# > 0)); do + case "$1" in + --sync-only) + doValidate=0 + ;; + --validate-only) + doSync=0 + ;; + -h | --help) + echo "用法: bash ./run_local_skills.sh [--sync-only|--validate-only]" + exit 0 + ;; + *) + echo "[错误] 未知参数: $1" + echo "用法: bash ./run_local_skills.sh [--sync-only|--validate-only]" + exit 2 + ;; + esac + shift +done + +validator="/root/.codex/skills/.system/skill-creator/scripts/quick_validate.py" +if [[ ! -f "${validator}" ]]; then + echo "[错误] 未找到技能校验脚本: ${validator}" + exit 1 +fi + +mapfile -t skillDirs < <(find skills -mindepth 1 -maxdepth 1 -type d ! -name shared | sort) +if [[ ${#skillDirs[@]} -eq 0 ]]; then + echo "[信息] 未发现可处理的技能目录(skills/*,排除 skills/shared)" + exit 0 +fi + +echo "====================================================" +echo "本地 Skills 任务启动" +echo " - sync=${doSync}" +echo " - validate=${doValidate}" +echo " - skills=${#skillDirs[@]}" +echo "====================================================" + +if [[ ${doSync} -eq 1 ]]; then + echo "[阶段] 同步术语占位文档..." + for skillDir in "${skillDirs[@]}"; do + termFile="${skillDir}/references/terminology.md" + mkdir -p "$(dirname "${termFile}")" + if [[ ! -f "${termFile}" ]]; then + cat > "${termFile}" <<'EOF' +# 术语字典 + +- 统一术语定义复用共享文档:`../../shared/terminology.md`。 +- 如出现本技能专属术语,可在本文件追加扩展,不覆盖共享定义。 +EOF + echo " - synced ${termFile} (created)" + elif grep -Fq '../../shared/terminology.md' "${termFile}"; then + echo " - synced ${termFile} (already linked)" + else + tmpFile="$(mktemp)" + cat > "${tmpFile}" <<'EOF' +# 术语字典 + +- 统一术语定义复用共享文档:`../../shared/terminology.md`。 +- 如出现本技能专属术语,可在本文件追加扩展,不覆盖共享定义。 + +EOF + cat "${termFile}" >> "${tmpFile}" + mv "${tmpFile}" "${termFile}" + echo " - synced ${termFile} (prefixed)" + fi + done +fi + +if [[ ${doValidate} -eq 1 ]]; then + echo "[阶段] 校验技能结构与 agents 元数据..." + for skillDir in "${skillDirs[@]}"; do + skillFile="${skillDir}/SKILL.md" + openaiFile="${skillDir}/agents/openai.yaml" + + python3 "${validator}" "${skillDir}" >/dev/null + + if [[ ! -f "${openaiFile}" ]]; then + echo "[错误] 缺少 agents/openai.yaml: ${openaiFile}" + exit 1 + fi + + if ! rg -q '^[[:space:]]*display_name:' "${openaiFile}"; then + echo "[错误] 缺少 interface.display_name: ${openaiFile}" + exit 1 + fi + if ! rg -q '^[[:space:]]*short_description:' "${openaiFile}"; then + echo "[错误] 缺少 interface.short_description: ${openaiFile}" + exit 1 + fi + if ! rg -q '^[[:space:]]*default_prompt:' "${openaiFile}"; then + echo "[错误] 缺少 interface.default_prompt: ${openaiFile}" + exit 1 + fi + + skillName="$(awk ' + BEGIN { inFm=0 } + /^---[[:space:]]*$/ { if (inFm==0) { inFm=1; next } else { exit } } + inFm==1 && /^name:[[:space:]]*/ { + sub(/^name:[[:space:]]*/, "", $0) + print $0 + exit + } + ' "${skillFile}")" + + if [[ -z "${skillName}" ]]; then + echo "[错误] 未能从 ${skillFile} 读取 name" + exit 1 + fi + + if ! grep -Fq "\$${skillName}" "${openaiFile}"; then + echo "[错误] default_prompt 未引用 \$${skillName}: ${openaiFile}" + exit 1 + fi + + echo " - valid ${skillDir}" + done +fi + +echo "====================================================" +echo "本地 Skills 任务完成" +echo "====================================================" diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..1c0b3d0 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,27 @@ +# 脚本目录说明 + +## 目录结构 +- `scripts/ci`:CI 与覆盖率相关核心脚本 +- `scripts/setup`:本地环境准备脚本 + +## 脚本清单 +- `scripts/ci/runBaseCoverage.sh` + - 单元测试矩阵入口(`quick/nightly/full`) + - 支持 `UNIT_SKIP_COV`、`UNIT_STOP_ON_FAIL`、`XMAKE_FORCE_CLEAN` +- `scripts/ci/runCoverage.sh` + - Fuzzer 入口(`quick/nightly/full`) + - 支持 `FUZZ_RUNS/FUZZ_MAX_TOTAL_TIME`、`FUZZ_WORKERS/JOBS`、`XMAKE_FORCE_CLEAN` +- `scripts/setup/install_qemu_deps.sh` + - 安装 `arm-none-eabi` 工具链与 `qemu-system-arm` + - 支持 `--no-update` 跳过包索引刷新 + +## 根目录本地脚本 +- `run_local_base.sh` + - 本地一键跑单元测试矩阵(默认 full) +- `run_local_ci.sh` + - 本地一键模拟 `ci-pr`(先 unit,再 fuzz quick) +- `run_local_fuzz.sh` + - 本地一键 fuzz +- `run_local_qemu.sh` + - 本地一键跑 QEMU 硬件语义校验(完整 localbase 单测 + 非对齐 fault) + - 默认 `QEMU_MEMORY=64M`,可按需通过环境变量覆盖 diff --git a/scripts/ci/runBaseCoverage.sh b/scripts/ci/runBaseCoverage.sh new file mode 100755 index 0000000..8125202 --- /dev/null +++ b/scripts/ci/runBaseCoverage.sh @@ -0,0 +1,213 @@ +#!/bin/bash +set -euo pipefail + +# 单元测试配置矩阵入口(Linux)。 +# 脚本路径:scripts/ci/runBaseCoverage.sh +# 执行模式:UNIT_MODE=quick|nightly|full +# quick: 2 组(PR 快检) +# nightly: 4 组(strict × addAtHead,scientific 固定 true) +# full: 8 组(三个布尔宏全组合) +# 常用参数: +# UNIT_SKIP_COV=0|1:是否跳过覆盖率 +# UNIT_STOP_ON_FAIL=0|1:失败是否立即终止 +# XMAKE_FORCE_CLEAN=0|1:每组前是否先清理配置 + +unitMode="${UNIT_MODE:-full}" +unitSkipCov="${UNIT_SKIP_COV:-0}" +unitStopOnFail="${UNIT_STOP_ON_FAIL:-1}" +xmakeForceClean="${XMAKE_FORCE_CLEAN:-0}" + +# 统一切到仓库根目录,避免从任意 cwd 启动时相对路径失效 +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repoRoot="$(cd "${scriptDir}/../.." && pwd)" +cd "${repoRoot}" + +# 覆盖率目录固定为 coverage/unitMatrix,每次执行前清理,保证只保留最新结果 +coverageRoot="coverage/unitMatrix" +rm -rf "${coverageRoot}" +profileRoot="${coverageRoot}/profiles" +mkdir -p "${profileRoot}" + +declare -a caseList=() + +addCase() { + local strictKey="$1" + local addAtHead="$2" + local scientific="$3" + caseList+=("${strictKey} ${addAtHead} ${scientific}") +} + +# 根据模式生成组合列表 +case "${unitMode}" in + quick) + # PR 快检:默认组合 + 对立组合 + addCase false true true + addCase true false true + ;; + nightly) + # 夜间:覆盖 strict × addAtHead 四种核心语义 + for strictKey in false true; do + for addAtHead in false true; do + addCase "${strictKey}" "${addAtHead}" true + done + done + ;; + full) + # 全量:三个布尔宏全组合 + for strictKey in false true; do + for addAtHead in false true; do + for scientific in false true; do + addCase "${strictKey}" "${addAtHead}" "${scientific}" + done + done + done + ;; + *) + echo "[错误] UNIT_MODE 仅支持 quick/nightly/full,当前值:${unitMode}" + exit 1 + ;; +esac + +totalCases="${#caseList[@]}" +caseIndex=0 +failedCases=0 + +runCase() { + local index="$1" + local total="$2" + local strictKey="$3" + local addAtHead="$4" + local scientific="$5" + + local caseName="strict_${strictKey}__head_${addAtHead}__sci_${scientific}" + local profraw="${profileRoot}/${caseName}.profraw" + + echo "====================================================" + echo "【用例 ${index}/${total}】${caseName}" + echo " - RyanJsonStrictObjectKeyCheck=${strictKey}" + echo " - RyanJsonDefaultAddAtHead=${addAtHead}" + echo " - RyanJsonSnprintfSupportScientific=${scientific}" + echo "====================================================" + + export RYANJSON_STRICT_OBJECT_KEY_CHECK="${strictKey}" + export RYANJSON_DEFAULT_ADD_AT_HEAD="${addAtHead}" + export RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC="${scientific}" + + # 重新配置,确保宏变化进入编译命令 + # 默认走增量配置,配合第三方静态库可减少重复编译 + if [[ "${xmakeForceClean}" == "1" ]]; then + echo "[阶段] 正在执行 xmake 配置(clean 模式)..." + if ! xmake f -c; then + echo "[错误] xmake 配置失败:${caseName}" + return 1 + fi + else + echo "[阶段] 正在执行 xmake 配置(增量模式)..." + if ! xmake f; then + echo "[错误] xmake 配置失败:${caseName}" + return 1 + fi + fi + + echo "[阶段] 正在执行 xmake 构建(target=RyanJson)..." + if ! xmake -b RyanJson; then + echo "[错误] xmake 构建失败:${caseName}" + return 1 + fi + + # 单测执行,profile 分文件隔离,避免组合间互相覆盖 + echo "[阶段] 正在运行单元测试二进制..." + if ! LLVM_PROFILE_FILE="${profraw}" ./build/linux/x86/release/RyanJson; then + echo "[错误] 单元测试执行失败:${caseName}" + return 1 + fi + + # 快检模式可跳过覆盖率阶段以缩短总时长 + if [[ "${unitSkipCov}" == "1" ]]; then + echo "[信息] UNIT_SKIP_COV=1,已跳过覆盖率生成。" + return 0 + fi +} + +# 按组合清单执行 +for entry in "${caseList[@]}"; do + caseIndex=$((caseIndex + 1)) + read -r strictKey addAtHead scientific <<< "${entry}" + + if runCase "${caseIndex}" "${totalCases}" "${strictKey}" "${addAtHead}" "${scientific}"; then + : + else + failedCases=$((failedCases + 1)) + if [[ "${unitStopOnFail}" == "1" ]]; then + echo + echo "[错误] 用例 ${caseIndex}/${totalCases} 失败,已按 UNIT_STOP_ON_FAIL=1 提前终止。" + exit 1 + fi + fi +done + +# 若启用覆盖率,则把矩阵中所有组合的 profraw 合并后只生成一份报告 +if [[ "${unitSkipCov}" != "1" ]]; then + if ! command -v llvm-profdata >/dev/null 2>&1; then + echo "[错误] 未找到 llvm-profdata,无法生成覆盖率。" + exit 1 + fi + if ! command -v llvm-cov >/dev/null 2>&1; then + echo "[错误] 未找到 llvm-cov,无法生成覆盖率。" + exit 1 + fi + + shopt -s nullglob + profrawFiles=("${profileRoot}"/*.profraw) + shopt -u nullglob + if [[ "${#profrawFiles[@]}" -eq 0 ]]; then + echo "[错误] 未找到可合并的 profraw 文件。" + exit 1 + fi + + mergedProfdata="${coverageRoot}/coverage.profdata" + reportTxt="${coverageRoot}/report.txt" + reportHtml="${coverageRoot}/html" + + if ! llvm-profdata merge -sparse "${profrawFiles[@]}" -o "${mergedProfdata}"; then + echo "[错误] profraw 合并失败。" + exit 1 + fi + + if ! llvm-cov report ./build/linux/x86/release/RyanJson \ + -instr-profile="${mergedProfdata}" \ + -show-mcdc-summary \ + -sources ./RyanJson > "${reportTxt}"; then + echo "[错误] 文本覆盖率生成失败。" + exit 1 + fi + + if ! llvm-cov show ./build/linux/x86/release/RyanJson \ + -instr-profile="${mergedProfdata}" \ + -format=html \ + -output-dir="${reportHtml}" \ + -show-mcdc-summary \ + -show-branches=count \ + -show-expansions \ + -show-regions \ + -show-line-counts-or-regions \ + -sources ./RyanJson; then + echo "[错误] HTML 覆盖率生成失败。" + exit 1 + fi +fi + +echo +echo "单元测试矩阵执行完成。" +echo "执行模式:${unitMode}" +echo "总用例数:${totalCases}" +echo "失败用例数:${failedCases}" +echo "覆盖率输出目录:${coverageRoot}" +if [[ "${unitSkipCov}" != "1" ]]; then + echo "覆盖率文本报告:${coverageRoot}/report.txt" + echo "覆盖率HTML目录:${coverageRoot}/html" +fi + +if [[ "${failedCases}" -gt 0 ]]; then + exit 1 +fi diff --git a/scripts/ci/runCoverage.sh b/scripts/ci/runCoverage.sh new file mode 100755 index 0000000..b89067b --- /dev/null +++ b/scripts/ci/runCoverage.sh @@ -0,0 +1,226 @@ +#!/bin/bash +set -euo pipefail + +# Fuzzer 入口脚本(Linux)。 +# 脚本路径:scripts/ci/runCoverage.sh +# 执行模式:FUZZ_MODE=quick|nightly|full +# quick: PR 快检(默认 60s) +# nightly: 夜间巡检(默认 300s) +# full: 全量压测(默认 900s) +# 常用参数: +# FUZZ_SKIP_COV=0|1:是否跳过覆盖率 +# FUZZ_RUNS=:固定迭代次数(优先于 max_total_time) +# FUZZ_MAX_TOTAL_TIME=<秒>:总时间预算 +# FUZZ_WORKERS / FUZZ_JOBS:并行 worker/job 数 +# FUZZ_TIMEOUT / FUZZ_MAX_LEN / FUZZ_VERBOSITY:透传给 libFuzzer +# FUZZ_RSS_LIMIT_MB / FUZZ_MALLOC_LIMIT_MB:内存预算(透传给 libFuzzer) +# FUZZ_CORPUS_DIR / FUZZ_DICT_PATH:corpus 与字典路径 +# FUZZ_EXTRA_ARGS:附加 libFuzzer 参数(空格分割) +# XMAKE_FORCE_CLEAN=0|1:是否在配置前先清理 + +fuzzMode="${FUZZ_MODE:-quick}" +fuzzSkipCov="${FUZZ_SKIP_COV:-0}" +fuzzTimeout="${FUZZ_TIMEOUT:-4}" +fuzzMaxLen="${FUZZ_MAX_LEN:-8192}" +fuzzVerbosity="${FUZZ_VERBOSITY:-0}" +fuzzCorpusDir="${FUZZ_CORPUS_DIR:-./test/fuzzer/corpus}" +fuzzDictPath="${FUZZ_DICT_PATH:-./test/fuzzer/RyanJsonFuzzer.dict}" +fuzzRuns="${FUZZ_RUNS:-}" +fuzzMaxTotalTime="${FUZZ_MAX_TOTAL_TIME:-}" +fuzzWorkers="${FUZZ_WORKERS:-}" +fuzzJobs="${FUZZ_JOBS:-}" +fuzzRssLimitMb="${FUZZ_RSS_LIMIT_MB:-}" +fuzzMallocLimitMb="${FUZZ_MALLOC_LIMIT_MB:-}" +xmakeForceClean="${XMAKE_FORCE_CLEAN:-0}" + +# 统一切到仓库根目录,避免从任意 cwd 启动时相对路径失效 +scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repoRoot="$(cd "${scriptDir}/../.." && pwd)" +cd "${repoRoot}" + +# 按模式设置默认预算(可被环境变量覆盖) +case "${fuzzMode}" in + quick) + defaultMaxTotalTime=60 + defaultWorkers=2 + defaultJobs=2 + ;; + nightly) + defaultMaxTotalTime=300 + defaultWorkers=4 + defaultJobs=4 + ;; + full) + defaultMaxTotalTime=900 + defaultWorkers=6 + defaultJobs=6 + ;; + *) + echo "[错误] FUZZ_MODE 仅支持 quick/nightly/full,当前值:${fuzzMode}" + exit 1 + ;; +esac + +# 如果用户没显式指定并行参数,则使用模式默认值 +if [[ -z "${fuzzWorkers}" ]]; then + fuzzWorkers="${defaultWorkers}" +fi +if [[ -z "${fuzzJobs}" ]]; then + fuzzJobs="${defaultJobs}" +fi + +# 如果没设置 FUZZ_RUNS,则走 max_total_time 模式 +if [[ -z "${fuzzRuns}" ]]; then + if [[ -z "${fuzzMaxTotalTime}" ]]; then + fuzzMaxTotalTime="${defaultMaxTotalTime}" + fi +fi + +strictKey="${RYANJSON_STRICT_OBJECT_KEY_CHECK:-false}" +addAtHead="${RYANJSON_DEFAULT_ADD_AT_HEAD:-true}" +scientific="${RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC:-true}" +caseName="strict_${strictKey}__head_${addAtHead}__sci_${scientific}" + +# 覆盖率目录固定为 coverage/fuzz,每次执行前清理,保证只保留最新结果 +coverageRoot="coverage/fuzz" +rm -rf "${coverageRoot}" +profileRoot="${coverageRoot}/profiles" +mkdir -p "${profileRoot}" + +profraw="${profileRoot}/coverage.profraw" +profdata="${coverageRoot}/coverage.profdata" +reportTxt="${coverageRoot}/report.txt" +reportHtml="${coverageRoot}/html" + +echo "====================================================" +echo "Fuzzer 执行配置" +echo " - 模式: ${fuzzMode}" +echo " - 配置: ${caseName}" +echo " - Corpus: ${fuzzCorpusDir}" +echo " - 字典: ${fuzzDictPath}" +echo " - timeout: ${fuzzTimeout}" +echo " - max_len: ${fuzzMaxLen}" +echo " - workers/jobs: ${fuzzWorkers}/${fuzzJobs}" +if [[ -n "${fuzzRssLimitMb}" ]]; then + echo " - rss_limit_mb: ${fuzzRssLimitMb}" +fi +if [[ -n "${fuzzMallocLimitMb}" ]]; then + echo " - malloc_limit_mb: ${fuzzMallocLimitMb}" +fi +if [[ -n "${fuzzRuns}" ]]; then + echo " - runs: ${fuzzRuns}" +else + echo " - max_total_time: ${fuzzMaxTotalTime}s" +fi +echo "====================================================" + +# 重新配置,确保宏变化进入编译命令 +if [[ "${xmakeForceClean}" == "1" ]]; then + echo "[阶段] 正在执行 xmake 配置(clean 模式)..." + xmake f -c +else + echo "[阶段] 正在执行 xmake 配置(增量模式)..." + xmake f +fi +echo "[阶段] 正在执行 xmake 构建(target=RyanJsonFuzz)..." +xmake -b RyanJsonFuzz +echo "[信息] xmake 构建完成(target=RyanJsonFuzz)" + +# corpus 不存在时自动创建,方便首次运行 +if [[ ! -d "${fuzzCorpusDir}" ]]; then + mkdir -p "${fuzzCorpusDir}" + echo "[信息] Corpus 目录不存在,已自动创建:${fuzzCorpusDir}" +fi + +# 字典文件可选:存在就启用,不存在就跳过 +declare -a dictArgs=() +if [[ -f "${fuzzDictPath}" ]]; then + dictArgs+=("-dict=${fuzzDictPath}") +else + echo "[警告] 未找到字典文件 ${fuzzDictPath},已跳过 -dict 参数。" +fi + +# 组装 libFuzzer 参数数组,避免字符串拼接导致转义问题 +declare -a fuzzArgs=() +fuzzArgs+=("${fuzzCorpusDir}") +fuzzArgs+=("${dictArgs[@]}") +fuzzArgs+=("-timeout=${fuzzTimeout}") +fuzzArgs+=("-verbosity=${fuzzVerbosity}") +fuzzArgs+=("-max_len=${fuzzMaxLen}") +fuzzArgs+=("-workers=${fuzzWorkers}") +fuzzArgs+=("-jobs=${fuzzJobs}") +if [[ -n "${fuzzRssLimitMb}" ]]; then + fuzzArgs+=("-rss_limit_mb=${fuzzRssLimitMb}") +fi +if [[ -n "${fuzzMallocLimitMb}" ]]; then + fuzzArgs+=("-malloc_limit_mb=${fuzzMallocLimitMb}") +fi + +if [[ -n "${fuzzRuns}" ]]; then + fuzzArgs+=("-runs=${fuzzRuns}") +else + fuzzArgs+=("-max_total_time=${fuzzMaxTotalTime}") +fi + +# 允许 CI 透传少量临时参数,例如 -rss_limit_mb=4096 +if [[ -n "${FUZZ_EXTRA_ARGS:-}" ]]; then + # shellcheck disable=SC2206 + extraArgs=( ${FUZZ_EXTRA_ARGS} ) + fuzzArgs+=("${extraArgs[@]}") +fi + +# 运行 fuzz。使用独立 profile 文件,避免多次执行互相覆盖 +echo "[阶段] 正在运行 fuzz 二进制..." +LLVM_PROFILE_FILE="${profraw}" ./build/linux/x86/release/RyanJsonFuzz "${fuzzArgs[@]}" + +# 快检模式可跳过覆盖率阶段以缩短总时长 +if [[ "${fuzzSkipCov}" == "1" ]]; then + echo "[信息] FUZZ_SKIP_COV=1,已跳过覆盖率生成。" + echo "输出目录:${coverageRoot}" + exit 0 +fi + +# 覆盖率工具检查:未安装时给出明确错误 +if ! command -v llvm-profdata >/dev/null 2>&1; then + echo "[错误] 未找到 llvm-profdata,无法生成覆盖率。" + exit 1 +fi +if ! command -v llvm-cov >/dev/null 2>&1; then + echo "[错误] 未找到 llvm-cov,无法生成覆盖率。" + exit 1 +fi + +# 合并 profile 数据 +llvm-profdata merge -sparse "${profraw}" -o "${profdata}" + +# 文本汇总覆盖率: +# 先原样打印到终端(尽量保留颜色) +# 再单独写入文件,便于归档 +echo "---------------- 覆盖率摘要(llvm-cov report) ----------------" +llvm-cov report ./build/linux/x86/release/RyanJsonFuzz \ + -instr-profile="${profdata}" \ + -show-mcdc-summary \ + --use-color \ + -sources ./RyanJson + +llvm-cov report ./build/linux/x86/release/RyanJsonFuzz \ + -instr-profile="${profdata}" \ + -show-mcdc-summary \ + -sources ./RyanJson > "${reportTxt}" + +# HTML 详细覆盖率(用于本地/制品追踪) +llvm-cov show ./build/linux/x86/release/RyanJsonFuzz \ + -instr-profile="${profdata}" \ + -format=html \ + -output-dir="${reportHtml}" \ + -show-mcdc-summary \ + -show-branches=count \ + -show-expansions \ + -show-regions \ + -show-line-counts-or-regions \ + -sources ./RyanJson + +echo "[完成] Fuzzer 执行结束,覆盖率已生成。" +echo "输出目录:${coverageRoot}" +echo "覆盖率文本报告:${reportTxt}" +echo "覆盖率HTML目录:${reportHtml}" diff --git a/scripts/setup/install_qemu_deps.sh b/scripts/setup/install_qemu_deps.sh new file mode 100755 index 0000000..cc783f3 --- /dev/null +++ b/scripts/setup/install_qemu_deps.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Install ARM bare-metal toolchain + QEMU needed by RyanJsonQemu. +# Supported package managers: apt, dnf, yum, pacman, brew. + +SCRIPT_NAME="$(basename "$0")" +NO_UPDATE="0" + +usage() { + cat </dev/null 2>&1; then + SUDO="sudo" + else + echo "[ERROR] sudo is required when not running as root." + exit 1 + fi +fi + +log() { + echo "[install_qemu_deps] $*" +} + +have_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +install_with_apt() { + local -a pkgs=( + gcc-arm-none-eabi + binutils-arm-none-eabi + qemu-system-arm + qemu-utils + ) + + if [[ "${NO_UPDATE}" != "1" ]]; then + log "apt-get update" + ${SUDO} apt-get update + fi + + log "apt-get install: ${pkgs[*]}" + DEBIAN_FRONTEND=noninteractive ${SUDO} apt-get install -y "${pkgs[@]}" +} + +install_with_dnf() { + local -a pkgs=( + arm-none-eabi-gcc-cs + arm-none-eabi-binutils-cs + qemu-system-arm + ) + + if [[ "${NO_UPDATE}" != "1" ]]; then + log "dnf makecache" + ${SUDO} dnf -y makecache + fi + + log "dnf install: ${pkgs[*]}" + ${SUDO} dnf install -y "${pkgs[@]}" +} + +install_with_yum() { + local -a pkgs=( + arm-none-eabi-gcc-cs + arm-none-eabi-binutils-cs + qemu-system-arm + ) + + if [[ "${NO_UPDATE}" != "1" ]]; then + log "yum makecache" + ${SUDO} yum -y makecache + fi + + log "yum install: ${pkgs[*]}" + ${SUDO} yum install -y "${pkgs[@]}" +} + +install_with_pacman() { + local -a pkgs=( + arm-none-eabi-gcc + arm-none-eabi-binutils + qemu-system-arm + ) + + if [[ "${NO_UPDATE}" != "1" ]]; then + log "pacman -Sy" + ${SUDO} pacman -Sy --noconfirm + fi + + log "pacman install: ${pkgs[*]}" + ${SUDO} pacman -S --noconfirm --needed "${pkgs[@]}" +} + +install_with_brew() { + local -a pkgs=( + arm-none-eabi-gcc + qemu + ) + + log "brew install: ${pkgs[*]}" + brew install "${pkgs[@]}" +} + +install_deps() { + if have_cmd apt-get; then + install_with_apt + return + fi + if have_cmd dnf; then + install_with_dnf + return + fi + if have_cmd yum; then + install_with_yum + return + fi + if have_cmd pacman; then + install_with_pacman + return + fi + if have_cmd brew; then + install_with_brew + return + fi + + echo "[ERROR] Unsupported package manager. Please install manually:" + echo " - arm-none-eabi-gcc" + echo " - arm-none-eabi-objcopy (binutils)" + echo " - qemu-system-arm" + exit 1 +} + +verify() { + local missing=0 + for cmd in arm-none-eabi-gcc arm-none-eabi-objcopy qemu-system-arm; do + if have_cmd "${cmd}"; then + log "found ${cmd}: $(command -v "${cmd}")" + else + echo "[ERROR] missing command after install: ${cmd}" + missing=1 + fi + done + + if [[ "${missing}" -ne 0 ]]; then + exit 1 + fi + + arm-none-eabi-gcc --version | head -n 1 + arm-none-eabi-objcopy --version | head -n 1 + qemu-system-arm --version | head -n 1 + + log "Dependencies are ready." +} + +install_deps +verify diff --git a/skills/ryanjson-api-usage/SKILL.md b/skills/ryanjson-api-usage/SKILL.md new file mode 100644 index 0000000..04e5aa9 --- /dev/null +++ b/skills/ryanjson-api-usage/SKILL.md @@ -0,0 +1,55 @@ +--- +name: ryanjson-api-usage +description: 面向 RyanJson 公开 API 的 RT-Thread 落地技能。用于 Parse/Create/Add/Change/Replace/Detach/Delete、所有权与释放语义、以及 RyanJsonInitHooks 初始化集成。用户请求“RyanJson 怎么用”“RT-Thread 怎么集成”“失败时谁释放”时使用本技能。 +--- + +# RyanJson API 使用技能 + +## 技能定位 +- 面向公开 API 的“怎么用”问题,输出可直接落地的调用方案与代码片段。 +- 默认面向 RT-Thread 集成场景,强调初始化、失败回退和释放责任。 + +## 适用与切换 +- 适用:接口选型、调用顺序、所有权判断、集成模板。 +- 切换到 `ryanjson-test-engineering`:当目标是补测试、补覆盖、复现崩溃。 +- 切换到 `ryanjson-optimization`:当目标是内部实现、性能优化、回归门禁。 + +## 必读入口 +- 共享基线:`../shared/ryanJsonCommon.md` +- 注释规范:统一使用 Doxygen 风格,且类型名/字段语义名/API 名保持英文(见共享基线第 9 节) +- API 快速入口:`references/quickstart.md` +- 场景模板:`references/apiPatterns.md` +- 所有权细则:`references/ownershipAndErrors.md` + +## 执行入口 +- 代码规范:先执行 `bash ./run_local_format.sh --check --changed`,提交前执行 `bash ./run_local_format.sh`。 +- 行为回归:默认先跑 `bash ./run_local_base.sh`;涉及硬件语义(对齐/异常)再跑 `bash ./run_local_qemu.sh`;需要联动 fuzz 时执行 `bash ./run_local_ci.sh` 或 `bash ./run_local_fuzz.sh`。 + +## 执行流程 +1. 先按 `../shared/ryanJsonCommon.md` 确认宏与语义前提。 +2. 按用户场景选最小 API 组合(读取/构建/更新/替换/迁移)。 +3. 给出“成功路径 + 失败路径”的最小代码,不夹带内部实现细节。 +4. 明确分支级所有权:创建方、接管点、失败后释放责任。 +5. 给出可验证项:返回值、日志、内存统计或泄漏检查点。 + +## API 专项约束 +- `Get*` 先判空,再 `RyanJsonIsXXX` 判型。 +- 同类型更新优先 `Change*Value`,跨类型更新使用 `ReplaceByKey/ReplaceByIndex`。 +- `Add/Insert` 仅接收游离节点,禁止重复挂载。 +- 传输压缩优先非格式化打印:`RyanJsonPrint(..., RyanJsonFalse, ...)` 或 `RyanJsonPrintPreallocated(..., RyanJsonFalse, ...)`。 +- `RyanJsonMinify` 是文本清洗工具,不作为传输输出主路径。 + +## 输出格式 +1. 前提与结论:宏/环境前提 + API 选型。 +2. 最小代码:只用公开 API,包含失败分支。 +3. 所有权清单:逐分支说明谁负责释放。 +4. 验证建议:最小验证步骤与后续扩展方向。 + +## 参考导航 +- 完整 API:`references/apiReference.md` +- hooks 初始化:`references/hooksInitPolicy.md` +- RT-Thread 示例:`references/rtThreadExamples.md` +- 集成模板:`references/integrationTemplate.md` +- 排障与故障注入:`references/pitfallsAndDebug.md`、`references/faultInjectionPlaybook.md` +- 术语:`references/terminology.md` +- 本地压缩文档:`context.md`、`apiPatterns.md`、`ownership.md`、`sop.md` diff --git a/skills/ryanjson-api-usage/agents/gemini.md b/skills/ryanjson-api-usage/agents/gemini.md new file mode 100644 index 0000000..7aeab04 --- /dev/null +++ b/skills/ryanjson-api-usage/agents/gemini.md @@ -0,0 +1,40 @@ +# Gemini Skill Card + +名称:`ryanjson-api-usage` + +## 定位 +- 面向嵌入式 RT-Thread 场景的 RyanJson 公开 API 落地技能。 + +## 适用场景 +- 询问 RyanJson 公开 API 的正确调用方式。 +- 需要 Parse/Create/Add/Change/Replace/Detach/Delete 的可运行示例。 +- 需要明确失败路径、所有权和释放顺序。 + +## 输入建议 +- 目标操作:要实现的业务 Json 流程。 +- 平台约束:RT-Thread 线程模型、内存预算、是否固定缓冲。 +- 验收标准:返回值、日志、内存统计、协议输出。 + +## 硬约束 +- 任意 RyanJson API 前,必须先成功调用 `RyanJsonInitHooks`。 +- 默认只讲公开 API,不展开内部实现细节。 +- `Get*` 使用前必须判空并 `RyanJsonIsXXX` 判型。 +- 语义不明确时按 `example/ -> test/unityTest/ -> test/fuzzer/` 取证。 + +## 术语口径 +- 统一按 `../references/terminology.md`。 +- 输出必须显式区分:已验证/推断、可恢复错误/不可恢复错误、失败语义。 + +## 默认提示词 +使用 `$ryanjson-api-usage`,输出 RT-Thread 可落地的 RyanJson 公开 API 方案:强制 hooks 前置、明确所有权/释放路径、仅使用公开 API,并在语义不明确时按 `example -> unityTest -> fuzzer` 取证。 + +## 输出骨架 +1. 结论与接口选型(标注已验证/推断)。 +2. 最小可运行代码(RT-Thread 风格)。 +3. 失败路径与所有权说明(区分 Add/Insert 与 Replace)。 +4. 上板验证步骤与下一步建议。 + +## 依据(仓库内) +- `../references/apiReference.md` +- `../references/ownershipAndErrors.md` +- `../references/apiPatterns.md` diff --git a/skills/ryanjson-api-usage/agents/openai.yaml b/skills/ryanjson-api-usage/agents/openai.yaml new file mode 100644 index 0000000..f6af72a --- /dev/null +++ b/skills/ryanjson-api-usage/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "RyanJson API 使用" + short_description: "面向 RT-Thread 的公开 API 落地与所有权语义说明" + default_prompt: "使用 $ryanjson-api-usage 输出 RyanJson 公开 API 的可运行方案:强制 RyanJsonInitHooks 前置,严格区分成功/失败路径与所有权释放责任;语义不清时按 example -> unityTest -> fuzzer 取证,并标注已验证与推断。" diff --git a/skills/ryanjson-api-usage/apiPatterns.md b/skills/ryanjson-api-usage/apiPatterns.md new file mode 100644 index 0000000..e41956f --- /dev/null +++ b/skills/ryanjson-api-usage/apiPatterns.md @@ -0,0 +1,40 @@ +# RyanJson API 场景模式(压缩版) + +## 1. 作用 +- 按用户意图快速选公开 API 路径。 +- 回答时强制带上失败分支与所有权说明。 +- 完整版见 `references/apiPatterns.md`。 + +## 2. 先做三件事 +1. 先确认 `RyanJsonInitHooks`。 +2. 先确认宏前提:`RyanJsonStrictObjectKeyCheck`、`RyanJsonDefaultAddAtHead`。 +3. 先判定场景再给最小 API 组合。 + +## 3. 快速选型 +| 场景 | 推荐路径 | 关键风险 | +|---|---|---| +| 读取配置 | Parse + Get + IsXXX | 未判型直接取值 | +| 周期上报 | Create + Add + PrintPreallocated(..., RyanJsonFalse, ...) | 缓冲不足、清理遗漏 | +| 同类型更新 | Get + Change*Value | 跨类型误用 Change | +| 跨类型更新 | Create* + ReplaceBy* | Replace 失败后泄漏 | +| 子树迁移 | Detach* + Add/Insert | detach 后未接管 | +| 传输压缩 | Print(format=false) | 误把 Minify 当传输主路径 | +| 文本清洗 | Minify | `\0` 终止符假设错误 | + +## 4. 关键语义提醒 +- `Replace` 失败不消费 `newItem`,调用方需复用或释放。 +- `Add/Insert` 与 `Replace` 失败语义不能混用。 +- 传输压缩优先非格式化打印,不推荐“先格式化再 Minify”。 +- `RyanJsonDefaultAddAtHead=true` 时,追加顺序可能反转。 + +## 5. 输出模板 +1. 前提:宏前提 + hooks 前提。 +2. 路径:推荐 API 顺序(成功 + 失败)。 +3. 所有权:每个失败分支谁释放。 +4. 验证:返回值/日志/内存检查点。 + +## 6. 最小依据 +- `RyanJson/RyanJsonItem.c`:Add/Insert/Replace/Detach 失败语义 +- `RyanJson/RyanJson.c`:Minify 行为 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c` +- `test/unityTest/cases/utils/testPrint.c`、`test/unityTest/cases/utils/testUtils.c` diff --git a/skills/ryanjson-api-usage/context.md b/skills/ryanjson-api-usage/context.md new file mode 100644 index 0000000..3c89d59 --- /dev/null +++ b/skills/ryanjson-api-usage/context.md @@ -0,0 +1,37 @@ +# RyanJson API 语境(压缩版) + +## 1. 作用 +- 提供 API 解答的最小安全语境,防止跨宏/跨模式误答。 +- 执行与模式共性口径见 `../shared/ryanJsonCommon.md`。 +- 术语口径见 `../shared/terminology.md`。 + +## 2. 回答前必须检查 +- 当前源码中的: + - `RyanJsonStrictObjectKeyCheck` + - `RyanJsonDefaultAddAtHead` +- 当前仓库默认值(仅参考): + - `RyanJsonStrictObjectKeyCheck=false` + - `RyanJsonDefaultAddAtHead=true` + +## 3. hooks 基线 +- `RyanJsonInitHooks` 必须在任意 RyanJson API 之前执行。 +- hooks 初始化失败时必须立即中止 Json 路径。 + +## 4. API 安全基线 +- 读取路径:先判空,再判型,再取值。 +- 更新路径:同类型用 `Change*Value`,跨类型用 `ReplaceBy*`。 +- 结构路径:`Detach` 后必须重新挂载或显式释放。 + +## 5. 错误级别口径 +- 可恢复错误:输入非法、key 不存在、类型不符等,返回 false/NULL。 +- 不可恢复错误:内存破坏、双重释放、结构不变量损坏;在启用 `RyanJsonEnableAssert` 时可触发 assert。 + +## 6. 输出检查单 +1. 标明宏前提。 +2. 区分已验证/推断。 +3. 给出失败分支与释放动作。 + +## 7. 依据(仓库内) +- `RyanJson/RyanJsonConfig.h`:`RyanJsonStrictObjectKeyCheck`、`RyanJsonDefaultAddAtHead` 默认值 +- `RyanJson/RyanJson.c`:hooks 全局指针与 `RyanJsonInitHooks` +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`:失败所有权语义 diff --git a/skills/ryanjson-api-usage/ownership.md b/skills/ryanjson-api-usage/ownership.md new file mode 100644 index 0000000..c334ebc --- /dev/null +++ b/skills/ryanjson-api-usage/ownership.md @@ -0,0 +1,32 @@ +# RyanJson 所有权矩阵(压缩版) + +## 1. 作用 +- 统一调用方与库之间的所有权口径,减少泄漏与误删。 +- 术语口径见 `../shared/terminology.md`。 + +## 2. 核心规则 +### 2.1 Create 系列 +- `RyanJsonCreate*` 成功返回后,节点归调用方。 +- 调用方必须挂载或删除该节点。 + +### 2.2 Detach 系列 +- `RyanJsonDetach*` 成功返回后,节点归调用方。 +- 调用方必须重新挂载或删除该节点。 + +### 2.3 Replace 系列 +- 当前实现中,`ReplaceByKey/ByIndex` 失败不消费 `newItem`。 +- 调用方必须复用或 `RyanJsonDelete(newItem)`。 + +### 2.4 Print 系列 +- `RyanJsonPrint` 返回动态字符串,调用方必须 `RyanJsonFree`。 +- `RyanJsonPrintPreallocated` 使用调用方缓冲,不额外释放输出缓冲。 + +## 3. 关键提醒 +- `Add/Insert` 与 `Replace` 的失败语义不能混用。 +- 边界行为不确定时,必须回查当前头文件与测试。 +- 语义依据:`RyanJson/RyanJsonItem.c`、`test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`。 + +## 4. 失败分支检查单 +1. 每个创建对象是否在所有分支都有明确归属。 +2. API 失败时是否说明了所有权是否转移。 +3. 每块动态内存是否“且仅”释放一次。 diff --git a/skills/ryanjson-api-usage/references/apiPatterns.md b/skills/ryanjson-api-usage/references/apiPatterns.md new file mode 100644 index 0000000..20401d6 --- /dev/null +++ b/skills/ryanjson-api-usage/references/apiPatterns.md @@ -0,0 +1,114 @@ +# API 场景模式(回答优先) + +## 1. 用途 +- 本文用于“按用户意图快速选 API 路径”,减少回答时漏掉失败语义和释放责任。 +- 仅覆盖公开 API;内部实现问题转 `ryanjson-optimization`,测试回归问题转 `ryanjson-test-engineering`。 + +## 2. 回答前固定动作 +1. 先确认 `RyanJsonInitHooks` 在任何 Json API 前执行。 +2. 先确认宏前提:`RyanJsonStrictObjectKeyCheck`、`RyanJsonDefaultAddAtHead`。 +3. 先判定用户意图属于哪类场景,再给最小 API 组合。 + +## 3. 意图到路径 +| 用户意图 | 推荐路径 | 是否改结构 | 关键风险 | +|---|---|---|---| +| 读取配置字段 | Parse + Get + IsXXX | 否 | 未判型直接取值 | +| 周期上报/打包发送 | Create + Add + PrintPreallocated | 是 | 缓冲不足或清理遗漏 | +| 在线改值(同类型) | Get + Change*Value | 否 | 把跨类型更新误写成 Change | +| 字段类型切换 | Create* + ReplaceBy* | 是 | Replace 失败后 `newItem` 泄漏 | +| 子树迁移/重排 | Detach* + Add/Insert | 是 | detach 后未接管 | +| 重复 key 策略切换 | 宏配置 + 业务校验同步 | 视需求 | 宏与测试预期不一致 | +| 传输压缩输出 | Print(format=false) | 否 | 误把 Minify 当传输主路径 | +| 历史文本清洗 | Minify | 否 | `\0` 终止符假设错误 | + +## 4. 场景卡 + +### A. 读取配置(Parse + Get) +- 触发:启动配置读取、外部报文字段抽取。 +- 调用顺序: + 1. `RyanJsonInitHooks` + 2. `RyanJsonParse` + 3. `RyanJsonGetObjectByKey` + 4. `RyanJsonIsXXX` 后再 `RyanJsonGetXXXValue` + 5. `RyanJsonDelete(root)` +- 失败口径: + - `Parse == NULL`:输入非法或资源不足,可恢复错误。 + - key 缺失/类型不符:可恢复错误,不应走崩溃路径。 + +### B. 周期上报(Create + Add + PrintPreallocated) +- 触发:周期遥测、固定缓冲发送。 +- 调用顺序: + 1. `RyanJsonCreateObject` + 2. `RyanJsonAdd*ToObject` + 3. `RyanJsonPrintPreallocated(..., RyanJsonFalse, ...)` + 4. `RyanJsonDelete(root)` +- 失败口径: + - 任意 Add 失败:立即清理 root 后返回。 + - PrintPreallocated 失败:走降级路径并清理 root。 + +### C. 同类型热更新(Get + Change) +- 触发:仅变更 value,不变更字段类型。 +- 规则: + - 同类型更新使用 `Change*Value`。 + - 跨类型更新必须切换到 `ReplaceByKey/ReplaceByIndex`。 + +### D. 跨类型替换(Replace) +- 触发:例如 `int -> object`、`string -> array`。 +- 调用顺序: + 1. 构造 `newItem` + 2. 调用 `RyanJsonReplaceByKey/ByIndex` + 3. 失败时调用方复用或 `RyanJsonDelete(newItem)` +- 关键提醒: + - `Replace` 失败不消费 `newItem`,不能套用 `Add/Insert` 失败语义。 + +### E. 子树迁移(Detach + Add/Insert) +- 触发:把既有子树迁移到新父节点。 +- 调用顺序: + 1. `detached = RyanJsonDetachByKey/ByIndex` + 2. 判空并确认可重挂载 + 3. `RyanJsonAddItem*` / `RyanJsonInsert` + 4. 失败分支立即处理 `detached` 所有权 +- 关键提醒: + - detach 后必须“重挂”或“释放”,不能悬空。 + +### F. 重复 key 策略(StrictKey 宏) +- `RyanJsonStrictObjectKeyCheck=true`:重复 key 更严格。 +- `RyanJsonStrictObjectKeyCheck=false`:兼容性更高,但 key 查询可预测性下降。 +- 回答时必须显式写明宏前提,并提示测试预期需同步。 + +### G. 传输压缩输出(非格式化 Print) +- 推荐路径: + 1. 动态输出:`RyanJsonPrint(..., RyanJsonFalse, ...)`,发送后 `RyanJsonFree(str)`。 + 2. 固定缓冲:`RyanJsonPrintPreallocated(..., RyanJsonFalse, ...)`。 +- 关键提醒: + - 传输场景优先 `format=false`,不要默认走 `Minify`。 + +### H. 文本清洗(Minify) +- 适用:已有 Json 文本去空白/注释后再解析或对比。 +- 调用顺序: + 1. 准备可写缓冲 + 2. `ret = RyanJsonMinify(buf, textLen)` + 3. `ret < textLen` 才可直接按 C 字符串使用 +- 关键提醒: + - `Minify` 是文本清洗工具,不是传输输出主路径。 + +## 5. 输出模板(回答时) +1. 前提:宏前提、hooks 前提、输入类型。 +2. 路径:推荐 API 顺序(成功路径 + 失败路径)。 +3. 所有权:每个失败分支由谁释放。 +4. 验证:最小可执行检查点(返回值/日志/内存)。 + +## 6. 高频误答拦截 +1. 未判型直接 `GetXXXValue`。 +2. 把 `Replace` 失败当成库自动清理。 +3. 传输路径建议先格式化再 Minify。 +4. 忽略 `RyanJsonDefaultAddAtHead` 导致索引/遍历顺序误判。 +5. 示例只写成功路径,不写失败与释放路径。 + +## 7. 依据(仓库内) +- `RyanJson/RyanJsonItem.c`:Add/Insert/Replace/Detach 失败与所有权路径 +- `RyanJson/RyanJson.c`:`RyanJsonMinify` 行为 +- `test/unityTest/cases/core/testCreate.c`:Add/Insert/Detach 相关断言 +- `test/unityTest/cases/core/testReplace.c`:Replace 失败不消费 `item` +- `test/unityTest/cases/utils/testPrint.c`:非格式化打印与 preallocated 边界 +- `test/unityTest/cases/utils/testUtils.c`、`test/unityTest/cases/utils/testRobust.c`、`test/fuzzer/cases/fuzzerMinify.c`:Minify 边界与稳健性 diff --git a/skills/ryanjson-api-usage/references/apiReference.md b/skills/ryanjson-api-usage/references/apiReference.md new file mode 100644 index 0000000..ffc0e3d --- /dev/null +++ b/skills/ryanjson-api-usage/references/apiReference.md @@ -0,0 +1,140 @@ +# RyanJson API 说明(公开接口,使用导向) + +## 范围 +- 本页是公开 API 的语义速查,不展开内部实现优化细节。 +- 场景化路径优先看 `apiPatterns.md`,所有权细节看 `ownershipAndErrors.md`。 +- hooks 与平台接入看 `hooksInitPolicy.md`、`rtThreadExamples.md`。 + +## 0. 初始化与配置 +### `RyanJsonInitHooks(malloc, free, realloc)` +- 必须最先调用。 +- 成功返回 `RyanJsonTrue`,失败返回 `RyanJsonFalse`。 +- 失败后禁止继续调用 Parse/Create/Add/Replace/Print 等 API。 +- 依据:当前实现的 `jsonMalloc/jsonFree/jsonRealloc` 全局指针默认是 `NULL`(`RyanJson/RyanJson.c`)。 + +### `RyanJsonStrictObjectKeyCheck`(`RyanJsonConfig.h`) +- `true`:Object 下拒绝重复 key(Parse/Insert/ReplaceByIndex 等路径更严格)。 +- `false`:允许重复 key;按 key 查询/替换/删除通常命中第一个,语义由上层约束。 + +### `RyanJsonFree(void *block)` +- 用于释放 `RyanJsonPrint` 返回的动态字符串。 + +## 1. Parse 类 +### `RyanJsonParse(const char *text)` +- 输入 `\0` 结尾字符串。 +- 成功返回根节点,失败返回 `NULL`。 +- 成功返回值由调用方 `RyanJsonDelete`。 +- 默认等价于 `RyanJsonParseOptions(text, strlen(text), RyanJsonFalse, NULL)`。 +- **默认是非严格尾部模式**(允许尾部存在未消费数据),以当前实现为准。 + +### `RyanJsonParseOptions(text, size, requireNullTerminator, parseEndPtr)` +- 适合非 `\0` 缓冲区或精确控制解析终点。 +- `requireNullTerminator = RyanJsonTrue` 时,解析后仅允许尾部空白。 + +## 2. Create 类 +### 标量创建 +- `RyanJsonCreateNull(key)` +- `RyanJsonCreateBool(key, boolean)` +- `RyanJsonCreateInt(key, number)` +- `RyanJsonCreateDouble(key, number)` +- `RyanJsonCreateString(key, string)` + +### 容器创建 +- `RyanJsonCreateObject()` +- `RyanJsonCreateArray()` +- `RyanJsonCreateIntArray(numbers, count)` +- `RyanJsonCreateDoubleArray(numbers, count)` +- `RyanJsonCreateStringArray(strings, count)` + +语义: +- Create 成功后节点归调用者。 +- 节点未挂到父树前,异常路径必须由调用者释放。 + +## 3. Add / Insert 类 +### 常用语法糖 +- Object:`RyanJsonAddIntToObject` / `RyanJsonAddStringToObject` ... +- Array:`RyanJsonAddIntToArray` / `RyanJsonAddStringToArray` ... + +### `RyanJsonAddItemToObject(pJson, key, item)` / `RyanJsonAddItemToArray` +- `AddItem` 仅接受 `Array/Object` 节点。 +- 标量请使用 `AddInt/AddString/...`。 + +### `RyanJsonInsert(pJson, index, item)` +- `index=0` 头插。 +- `index=UINT32_MAX` 或越界可视为尾插。 +- Object 场景要求 `item` 带 key。 + +所有权: +- 成功:`item` 转移到父节点。 +- 失败: + - `item` 为游离节点时,`Add/Insert` 失败路径由库侧清理。 + - `item` 非游离节点时,直接返回 false,不接管释放(保护原树)。 + - `AddItemToObject` 传入标量时会直接失败并删除该标量节点(当前实现语义)。 + +## 4. Change 类(仅同类型改值) +- `RyanJsonChangeKey(pJson, key)` +- `RyanJsonChangeStringValue(pJson, strValue)` +- `RyanJsonChangeIntValue(pJson, number)` +- `RyanJsonChangeDoubleValue(pJson, number)` +- `RyanJsonChangeBoolValue(pJson, boolean)` + +规则: +- Change 会做基础入参/类型校验,失败返回 `RyanJsonFalse`。 +- Change 不做类型切换;类型切换请用 Replace。 + +## 5. Replace 类(类型切换主入口) +### `RyanJsonReplaceByIndex(pJson, index, item)` +- 数组/对象都可用(对象场景更推荐 `ReplaceByKey`)。 + +### `RyanJsonReplaceByKey(pJson, key, item)` +- 仅 Object。 +- `item` 无 key 时会按目标 key 包装。 +- `item` 有 key 且不同于目标 key 时会尝试改 key。 + +规则: +- 调用前要求 `item` 为游离节点(`RyanJsonIsDetachedItem`)。 +- 成功:旧节点被删除,新节点接管到树中。 +- 失败:默认不消费 `item`,调用方负责复用或释放。 + +## 6. Get / Has / 路径类 +- `RyanJsonGetObjectByKey` / `RyanJsonGetObjectByIndex` +- `RyanJsonHasObjectByKey` / `RyanJsonHasObjectByIndex` +- `RyanJsonGetObjectToKey` / `RyanJsonGetObjectToIndex` + +关键约束: +- `GetKey/GetString/GetInt/GetDouble/GetBool/GetObjectValue` 这类取值前,必须先判空并用 `RyanJsonIsXXX` 判型。 + +## 7. Detach / Delete 类 +### `RyanJsonDetachByKey/DetachByIndex` +- 从树中摘除节点并返回。 +- 返回节点归调用者,必须手动 `RyanJsonDelete`(或再次挂树)。 + +### `RyanJsonDeleteByKey/DeleteByIndex` +- 直接删除目标节点,不返回。 + +### `RyanJsonDelete(root)` +- 删除整棵树。 + +## 8. Print / Minify / Compare +- `RyanJsonPrint`:动态输出,返回值用 `RyanJsonFree`。 +- `RyanJsonPrintPreallocated`:预分配输出,适合 RT-Thread 固定缓冲。 +- 传输场景优先:`Print(..., RyanJsonFalse, ...)` / `PrintPreallocated(..., RyanJsonFalse, ...)` 直接输出紧凑 Json。 +- `RyanJsonMinify`:原地文本清洗(去空白/注释),用于已有 Json 文本处理,不作为首选传输输出路径。 +- `RyanJsonMinify` 终止符规则: + - 返回值 `< textLen`:会写入 `\0`; + - 返回值 `== textLen`:不会额外写入 `\0`,调用方需自行保证字符串终止空间。 +- `RyanJsonCompare` / `RyanJsonCompareOnlyKey` / `RyanJsonCompareDouble`。 + +## 依据(仓库内) +- `RyanJson/RyanJson.c`(`RyanJsonInitHooks` 全局 hooks 初始化) +- `RyanJson/RyanJsonParse.c`(`RyanJsonParse -> RyanJsonParseOptions(..., RyanJsonFalse, NULL)`) +- `RyanJson/RyanJsonItem.c`(`RyanJsonInsert` 失败清理、`RyanJsonAddItemToObject` 标量失败删除、`RyanJsonReplaceByKey/ByIndex` 失败不消费) +- `test/unityTest/cases/core/testCreate.c`(Insert/Add 失败语义与已挂树拒绝) +- `test/unityTest/cases/core/testReplace.c`(Replace 失败所有权) +- `test/unityTest/cases/utils/testPrint.c`(`format=false` 紧凑输出与 preallocated 行为) +- `RyanJson/RyanJson.c` 中 `RyanJsonMinify` 实现 +- `test/unityTest/cases/utils/testUtils.c`、`test/unityTest/cases/utils/testRobust.c`、`test/fuzzer/cases/fuzzerMinify.c` + +## 9. 对外/对内边界 +- `RyanJsonChangeObjectValue` 属于内部实现接口,不作为公开 API 使用。 +- 对外业务修改对象/数组内容,请通过 Add/Insert/Detach/Replace 组合完成。 diff --git a/skills/ryanjson-api-usage/references/faultInjectionPlaybook.md b/skills/ryanjson-api-usage/references/faultInjectionPlaybook.md new file mode 100644 index 0000000..480f40b --- /dev/null +++ b/skills/ryanjson-api-usage/references/faultInjectionPlaybook.md @@ -0,0 +1,72 @@ +# 故障注入与稳定性验证(主机侧可选) + +## 范围 +- 用于主机侧或实验环境验证失败路径。 +- RT-Thread 板端资源紧张时可不执行本流程。 +- 所有权口径见 `ownershipAndErrors.md`。 + +## 目标 +- 验证分配失败场景下的可恢复性。 +- 覆盖 Parse/Create/Add/Insert/Replace/Print 的失败分支。 + +## 注入策略 +- 固定第 N 次分配失败(稳定复现)。 +- 按概率失败(压力回归)。 +- 按调用点标签失败(精确定位)。 + +## 包装器示例 +```c +#include +#include +#include +#include "RyanJson.h" + +typedef struct +{ + uint32_t allocCount; + uint32_t failAt; +} jsonFaultState_t; + +static jsonFaultState_t gFaultState = {0U, 0U}; + +static void *faultMalloc(size_t size) +{ + gFaultState.allocCount++; + if ((0U != gFaultState.failAt) && (gFaultState.allocCount == gFaultState.failAt)) + { + return NULL; + } + return malloc(size); +} + +static void faultFree(void *ptr) +{ + free(ptr); +} + +static void *faultRealloc(void *ptr, size_t size) +{ + gFaultState.allocCount++; + if ((0U != gFaultState.failAt) && (gFaultState.allocCount == gFaultState.failAt)) + { + return NULL; + } + return realloc(ptr, size); +} +``` + +## 建议检查点 +- Parse 失败:返回 `NULL`,无泄漏。 +- Add/Insert 失败:返回 false;游离 item 由库侧清理,非游离 item 失败不消费。 +- Replace 失败:返回 false,item 仍由调用方持有(可继续复用或释放)。 +- Print 失败:返回 `NULL`,原 Json 树仍可正常释放。 + +## 结果判定 +- 无崩溃。 +- 无泄漏。 +- 返回值与所有权语义与头文件一致。 + +## 依据(仓库内) +- `RyanJson/RyanJsonItem.c`:`RyanJsonInsert`、`RyanJsonReplaceByKey/ByIndex` +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c` +- `test/fuzzer/cases/fuzzerCreate.c`、`test/fuzzer/cases/fuzzerReplace.c` diff --git a/skills/ryanjson-api-usage/references/geminiCompat.md b/skills/ryanjson-api-usage/references/geminiCompat.md new file mode 100644 index 0000000..d5157a2 --- /dev/null +++ b/skills/ryanjson-api-usage/references/geminiCompat.md @@ -0,0 +1,31 @@ +# Gemini 兼容说明(API 使用类) + +## 范围 +- 本页只定义 Gemini 在 API 使用类任务的输入/输出结构。 +- 公开 API 语义以 `apiReference.md`、`apiPatterns.md` 为准。 + +## 推荐输入结构 +- 目标:要实现的 Json 业务操作。 +- 平台:RT-Thread(线程模型、内存约束)。 +- 约束:是否允许额外内存、是否启用严格 key。 +- 验收:返回值、日志输出、内存统计。 + +## 推荐输出结构 +1. API 选型说明(仅公开 API)。 +2. RT-Thread 可运行示例代码(含 hooks 初始化)。 +3. 失败路径与所有权说明。 +4. 上板验证步骤(日志/返回值/内存统计)。 + +## 对齐原则 +- 必须把 `RyanJsonInitHooks` 作为前置条件写清楚。 +- 不默认要求用户在 RT-Thread 板端运行 unity/fuzzer。 +- API 语义不确定时,AI 可按 `example -> test/unityTest -> test/fuzzer` 取证。 +- API 技能默认不展开 RyanJson 内部实现细节。 + +## 术语字典(统一) +- 本文术语统一以 `terminology.md` 为准。 + +## 依据(仓库内) +- `apiReference.md`:公开 API 语义基线 +- `ownershipAndErrors.md`:失败所有权口径 +- `apiPatterns.md`:场景化调用顺序 diff --git a/skills/ryanjson-api-usage/references/hooksInitPolicy.md b/skills/ryanjson-api-usage/references/hooksInitPolicy.md new file mode 100644 index 0000000..4732315 --- /dev/null +++ b/skills/ryanjson-api-usage/references/hooksInitPolicy.md @@ -0,0 +1,34 @@ +# RyanJsonInitHooks 初始化规范(RT-Thread 导向) + +## 范围 +- 本页只定义 hooks 初始化策略与上板检查点。 +- 通用 API 调用顺序见 `quickstart.md`。 + +## 规则 +- `RyanJsonInitHooks` 是 RyanJson 前置条件,必须在任何 API 前调用。 +- 建议在系统启动阶段只初始化一次。 +- 初始化失败必须可观测(日志/错误码),并阻断后续 Json 逻辑。 + +## RT-Thread 推荐位置 +- 应用初始化阶段(如 `INIT_APP_EXPORT`)。 +- 或 Json owner 线程启动阶段(需保证先于业务调用)。 + +## 实现建议 +- 映射 `rt_malloc/rt_free/rt_realloc` 或自定义内存池封装。 +- 保证 `realloc` 失败返回 `NULL` 且不破坏原指针语义。 +- 建议记录分配失败次数和峰值占用,便于板端排障。 + +## 线程模型建议 +- 库本身不承诺线程安全。 +- 推荐单 owner 线程独占 Json 树。 +- 多线程共享时,用外层锁保护完整事务。 + +## 上板检查清单 +- hooks 是否在首次 Parse/Create 前成功执行。 +- hooks 失败时是否正确阻断业务路径。 +- 常用 API 成功/失败路径是否都能返回并正确释放。 + +## 依据(仓库内) +- `RyanJson/RyanJson.c`:`jsonMalloc/jsonFree/jsonRealloc` 为全局 hooks,默认 `NULL` +- `test/unityTest/common/testCommon.c`:测试入口先 `RyanJsonInitHooks(...)` +- `test/fuzzer/entry.c`:`LLVMFuzzerTestOneInput` 内先校验并初始化 hooks diff --git a/skills/ryanjson-api-usage/references/integrationTemplate.md b/skills/ryanjson-api-usage/references/integrationTemplate.md new file mode 100644 index 0000000..63eed8e --- /dev/null +++ b/skills/ryanjson-api-usage/references/integrationTemplate.md @@ -0,0 +1,66 @@ +# 集成模板(业务流程版) + +## 范围 +- 这是 API 技能的 **基线模板**:初始化 -> 解析/修改 -> 输出 -> 释放。 +- `quickstart.md` 与 `rtThreadExamples.md` 都可基于本模板裁剪。 +- 共性口径见 `../../shared/ryanJsonCommon.md`。 + +## 基线伪代码 +```c +#include +#include "RyanJson.h" + +static RyanJsonBool_e appEnsureJsonHooks(void) +{ + static RyanJsonBool_e inited = RyanJsonFalse; + if (RyanJsonTrue == inited) { return RyanJsonTrue; } + + inited = RyanJsonInitHooks(malloc, free, realloc); + return inited; +} + +RyanJsonBool_e appProcessJson(const char *input) +{ + RyanJson_t root = NULL; + char *output = NULL; + RyanJsonBool_e ok = RyanJsonFalse; + + if (RyanJsonFalse == appEnsureJsonHooks()) { goto done; } + + root = RyanJsonParse(input); + if (NULL == root) { goto done; } + + // modify/read json here + + output = RyanJsonPrint(root, 0, RyanJsonFalse, NULL); + if (NULL == output) { goto done; } + + ok = RyanJsonTrue; + +done: + if (NULL != output) { RyanJsonFree(output); } + if (NULL != root) { RyanJsonDelete(root); } + return ok; +} +``` + +## 裁剪建议 +- RTOS:把 `malloc/free/realloc` 替换为内存池包装器。 +- 高可靠:补错误码与日志等级;把 `goto done` 统一接入故障统计。 +- 高吞吐:优先 `RyanJsonPrintPreallocated`,减少动态分配。 + +## 线程模型建议 +- 推荐单 owner 任务独占 Json 树。 +- 若必须多任务访问,外层用互斥锁包住完整事务(Parse->Modify->Print->Delete)。 + +## 最小检查单 +1. hooks 初始化失败时是否中止事务。 +2. Parse/Print 失败分支是否都回收资源。 +3. 动态输出是否统一配对 `RyanJsonFree`。 +4. 事务是否有单一出口清理点。 + +## 依据(仓库内) +- `RyanJson/RyanJson.c`:hooks 全局指针与 `RyanJsonInitHooks` +- `RyanJson/RyanJsonPrint.c`:`RyanJsonPrint`/`RyanJsonPrintPreallocated` +- `test/unityTest/cases/utils/testPrint.c`:打印与预分配边界行为 +- `test/unityTest/cases/core/testReplace.c`:Replace 失败由调用方处理新节点 diff --git a/skills/ryanjson-api-usage/references/ownershipAndErrors.md b/skills/ryanjson-api-usage/references/ownershipAndErrors.md new file mode 100644 index 0000000..732c1e3 --- /dev/null +++ b/skills/ryanjson-api-usage/references/ownershipAndErrors.md @@ -0,0 +1,64 @@ +# 所有权与错误处理 + +## 范围 +- 本页只定义公开 API 的所有权与失败语义。 +- 术语口径见 `../../shared/terminology.md`。 + +## 所有权基线 +- `RyanJsonCreate*`:成功后归调用者。 +- `Add/Insert` 成功:`item` 转移给父节点。 +- `Replace` 成功:新 `item` 转移给父节点,旧节点由库删除。 +- `DetachByKey/DetachByIndex`:返回节点归调用者。 +- `RyanJsonPrint` 返回缓冲:调用者用 `RyanJsonFree` 释放。 + +## 失败语义(重点) +- `RyanJsonInsert` 失败: + - `item` 是游离节点时:失败路径由库删除该 `item`。 + - `item` 非游离节点时:直接返回 false,不删除(避免破坏原树)。 +- `RyanJsonAddItemToObject` 失败(当前实现): + - `item` 非游离:返回 false,不删除。 + - `item` 为标量:返回 false,并删除该 `item`。 + - `item` 为容器且后续插入失败:失败路径由库侧清理(包含包装节点与其子树)。 +- `ReplaceByKey/ReplaceByIndex` 失败:不消费 `item`,调用者决定复用或释放。 + +## 建议失败处理模板 +### Add/Insert +```c +RyanJson_t item = RyanJsonCreateObject(); +if ((NULL == item) || (RyanJsonFalse == RyanJsonIsDetachedItem(item))) { return RyanJsonFalse; } + +if (RyanJsonFalse == RyanJsonInsert(parent, UINT32_MAX, item)) +{ + // Insert 失败时,游离 item 会由库侧清理;不要盲目二次释放 + return RyanJsonFalse; +} +``` + +### Replace +```c +RyanJson_t newItem = RyanJsonCreateObject(); +if ((NULL == newItem) || (RyanJsonFalse == RyanJsonIsDetachedItem(newItem))) { return RyanJsonFalse; } + +if (RyanJsonFalse == RyanJsonReplaceByKey(parent, "k", newItem)) +{ + // Replace 失败不消费 item,调用者负责释放或复用 + RyanJsonDelete(newItem); + return RyanJsonFalse; +} +``` + +## 游离节点防误用 +- 复用节点前先 `RyanJsonIsDetachedItem(item)`。 +- 已挂树节点禁止再次 Add/Insert/Replace 到其它位置。 + +## 常见误区 +- 把 Add/Insert 与 Replace 的失败释放语义混为一套。 +- Detach 后未释放也未重新挂树。 +- hooks 未初始化就开始 Parse/Create。 + +## 依据(仓库内) +- `RyanJson/RyanJsonItem.c`:`RyanJsonInsert` 失败 `error__` 删除 `item` +- `RyanJson/RyanJsonItem.c`:`RyanJsonAddItemToObject` 对非游离/标量/包装失败路径分支 +- `RyanJson/RyanJsonItem.c`:`RyanJsonReplaceByKey/ByIndex` 失败路径返回 false,不删除新 `item` +- `test/unityTest/cases/core/testCreate.c`:Insert/AddItem 失败语义(含已挂树拒绝、标量失败) +- `test/unityTest/cases/core/testReplace.c`:Replace 失败后 `item` 仍游离并可复用/需调用方释放 diff --git a/skills/ryanjson-api-usage/references/pitfallsAndDebug.md b/skills/ryanjson-api-usage/references/pitfallsAndDebug.md new file mode 100644 index 0000000..4597723 --- /dev/null +++ b/skills/ryanjson-api-usage/references/pitfallsAndDebug.md @@ -0,0 +1,35 @@ +# 常见坑与排障(RT-Thread) + +## 范围 +- 本页是公开 API 视角排障,不展开内部实现优化细节。 +- 共性语义口径见 `../../shared/ryanJsonCommon.md`。 + +## 高发误用 +- 未先调用 `RyanJsonInitHooks` 就 Parse/Create。 +- `Get*` 前不做判空和 `RyanJsonIsXXX` 判型。 +- 把 `Change*Value` 当类型切换接口使用。 +- 已挂树节点重复 Add/Insert/Replace。 +- 忽略 `RyanJsonStrictObjectKeyCheck` 导致重复 key 语义误判。 +- Replace 失败后误判 `item` 所有权。 +- 误以为 `RyanJsonParse` 默认会严格校验尾部文本。 +- 把 `RyanJsonMinify` 输出无条件当作 `\0` 结尾字符串使用。 + +## 推荐排障顺序 +1. 检查 hooks 初始化是否先于任何 RyanJson API。 +2. 确认 `RyanJsonConfig.h` 关键宏(尤其严格 key)与预期一致。 +3. 还原最小输入,复现并记录返回值/日志。 +4. 核对失败分支的释放顺序(Create/Add/Replace/Detach/Print)。 +5. 需要语义取证时,按 `example -> unityTest -> fuzzer` 查证。 + +## 典型症状速查 +- `strcmp` 越界:优先排查 key 是否为合法终止字符串。 +- leak:优先排查 Replace/Detach 失败后的调用方释放逻辑。 +- 打印返回 NULL:优先检查预分配长度边界与输入树合法性。 +- Parse 明明“看起来成功”但后续仍有脏数据:检查是否需要 `RyanJsonParseOptions(..., RyanJsonTrue, ...)`。 +- Minify 后字符串拼接异常:检查返回值是否等于 `textLen`(此时不会自动补 `\0`)。 + +## 依据(仓库内) +- `RyanJson/RyanJsonParse.c`:Parse 默认非严格尾部语义 +- `RyanJson/RyanJsonItem.c`:Insert/Replace 失败所有权路径 +- `RyanJson/RyanJson.c`:`RyanJsonMinify` 终止符写入条件 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`、`test/unityTest/cases/utils/testUtils.c` diff --git a/skills/ryanjson-api-usage/references/quickstart.md b/skills/ryanjson-api-usage/references/quickstart.md new file mode 100644 index 0000000..f7b7137 --- /dev/null +++ b/skills/ryanjson-api-usage/references/quickstart.md @@ -0,0 +1,162 @@ +# RyanJson 快速上手(RT-Thread/C) + +## 范围 +- 本文提供“最小可跑通”的公开 API 路径。 +- 完整业务模板见 `integrationTemplate.md`。 +- 平台差异实现见 `rtThreadExamples.md`。 +- 失败所有权细则见 `ownershipAndErrors.md`。 + +## 入口条件(必须) +1. 任何 Json API 前先完成 `RyanJsonInitHooks`。 +2. 回答或示例中标明宏前提: + - `RyanJsonStrictObjectKeyCheck` + - `RyanJsonDefaultAddAtHead` +3. 每条失败分支都要显式释放(`RyanJsonDelete` / `RyanJsonFree`)。 + +## 示例 1:初始化 hooks(一次性) +```c +#include +#include "RyanJson.h" + +static RyanJsonBool_e appEnsureJsonHooks(void) +{ + static RyanJsonBool_e inited = RyanJsonFalse; + if (RyanJsonTrue == inited) { return RyanJsonTrue; } + + inited = RyanJsonInitHooks(malloc, free, realloc); + return inited; +} +``` + +## 示例 2:Parse + Get(安全读取) +```c +#include +#include "RyanJson.h" + +static RyanJsonBool_e readAge(const char *jsonText, int32_t *outAge) +{ + RyanJson_t root = NULL; + RyanJson_t ageItem = NULL; + + if ((NULL == jsonText) || (NULL == outAge)) { return RyanJsonFalse; } + if (RyanJsonFalse == appEnsureJsonHooks()) { return RyanJsonFalse; } + + root = RyanJsonParse(jsonText); + if (NULL == root) { return RyanJsonFalse; } + + ageItem = RyanJsonGetObjectByKey(root, "age"); + if ((NULL == ageItem) || (RyanJsonFalse == RyanJsonIsInt(ageItem))) + { + RyanJsonDelete(root); + return RyanJsonFalse; + } + + *outAge = RyanJsonGetIntValue(ageItem); + RyanJsonDelete(root); + return RyanJsonTrue; +} +``` + +## 示例 3:Create + Add + PrintPreallocated(非格式化输出) +```c +#include +#include "RyanJson.h" + +static RyanJsonBool_e buildReport(char *out, uint32_t outCap, uint32_t *outLen) +{ + RyanJson_t root = NULL; + + if ((NULL == out) || (NULL == outLen) || (0U == outCap)) { return RyanJsonFalse; } + + root = RyanJsonCreateObject(); + if (NULL == root) { return RyanJsonFalse; } + + if ((RyanJsonFalse == RyanJsonAddStringToObject(root, "name", "ryan")) || + (RyanJsonFalse == RyanJsonAddIntToObject(root, "age", 18))) + { + RyanJsonDelete(root); + return RyanJsonFalse; + } + + if (NULL == RyanJsonPrintPreallocated(root, out, outCap, RyanJsonFalse, outLen)) + { + RyanJsonDelete(root); + return RyanJsonFalse; + } + + RyanJsonDelete(root); + return RyanJsonTrue; +} +``` + +## 示例 4:Replace 跨类型切换(失败不消费 newItem) +```c +#include "RyanJson.h" + +static RyanJsonBool_e replaceFreqAsObject(RyanJson_t root) +{ + RyanJson_t newFreq = NULL; + + if (NULL == root) { return RyanJsonFalse; } + + newFreq = RyanJsonCreateObject(); + if (NULL == newFreq) { return RyanJsonFalse; } + if (RyanJsonFalse == RyanJsonIsDetachedItem(newFreq)) + { + RyanJsonDelete(newFreq); + return RyanJsonFalse; + } + + if ((RyanJsonFalse == RyanJsonAddIntToObject(newFreq, "value", 100)) || + (RyanJsonFalse == RyanJsonAddStringToObject(newFreq, "unit", "Hz"))) + { + RyanJsonDelete(newFreq); + return RyanJsonFalse; + } + + if (RyanJsonFalse == RyanJsonReplaceByKey(root, "freq", newFreq)) + { + RyanJsonDelete(newFreq); // Replace 失败:调用方负责释放 + return RyanJsonFalse; + } + + return RyanJsonTrue; +} +``` + +## 示例 5:Detach 后重挂或释放 +```c +#include "RyanJson.h" + +static RyanJsonBool_e movePayload(RyanJson_t src, RyanJson_t dst) +{ + RyanJson_t detached = NULL; + + if ((NULL == src) || (NULL == dst)) { return RyanJsonFalse; } + + detached = RyanJsonDetachByKey(src, "payload"); + if (NULL == detached) { return RyanJsonFalse; } + + if (RyanJsonFalse == RyanJsonAddItemToObject(dst, "payload", detached)) + { + // 当前实现下,对游离节点 AddItem 失败路径由库侧清理;不要二次释放 detached + return RyanJsonFalse; + } + + return RyanJsonTrue; +} +``` + +## 最小检查单 +1. hooks 初始化是否先于 Parse/Create。 +2. Get 前是否判空 + 判型。 +3. Replace 失败后是否由调用方处理 `newItem`。 +4. 动态打印返回值是否配对 `RyanJsonFree`。 +5. 退出分支是否都做了 `RyanJsonDelete`。 + +## 依据(仓库内) +- `RyanJson/RyanJson.c`:`RyanJsonInitHooks`、`RyanJsonDelete` +- `RyanJson/RyanJsonItem.c`:`RyanJsonReplaceByKey`、`RyanJsonDetachByKey`、`RyanJsonAddItemToObject` +- `test/unityTest/cases/core/testReplace.c`:Replace 失败不消费 `item` +- `test/unityTest/cases/core/testDetach.c`、`test/unityTest/cases/core/testCreate.c`:Detach/AddItem 失败路径 +- `test/unityTest/cases/utils/testPrint.c`:非格式化打印与 preallocated 边界 diff --git a/skills/ryanjson-api-usage/references/rtThreadExamples.md b/skills/ryanjson-api-usage/references/rtThreadExamples.md new file mode 100644 index 0000000..e998716 --- /dev/null +++ b/skills/ryanjson-api-usage/references/rtThreadExamples.md @@ -0,0 +1,151 @@ +# RT-Thread 平台示例(RyanJson) + +## 范围 +- 本页只放 RT-Thread 平台差异:hooks 映射、任务入口、日志与并发建议。 +- 通用 API 路径见 `quickstart.md` / `integrationTemplate.md`。 +- 失败语义与释放规则见 `ownershipAndErrors.md`。 + +## 接入建议 +1. 应用初始化阶段完成 hooks 初始化(如 `INIT_APP_EXPORT`)。 +2. 业务线程执行完整 Json 事务(Create/Parse -> Modify -> Print -> Delete)。 +3. 推荐单 owner 线程;多线程共享时在外层加锁保护完整事务。 + +## 示例 1:hooks 初始化(RT-Thread 映射) +```c +#include +#include "RyanJson.h" + +static void *rtJsonMalloc(size_t size) { return rt_malloc(size); } +static void rtJsonFree(void *ptr) +{ + if (RT_NULL != ptr) { rt_free(ptr); } +} +static void *rtJsonRealloc(void *ptr, size_t size) { return rt_realloc(ptr, size); } + +static int32_t rtJsonInit(void) +{ + if (RyanJsonFalse == RyanJsonInitHooks(rtJsonMalloc, rtJsonFree, rtJsonRealloc)) + { + rt_kprintf("RyanJsonInitHooks failed\n"); + return -RT_ERROR; + } + + rt_kprintf("RyanJson hooks ready\n"); + return RT_EOK; +} +INIT_APP_EXPORT(rtJsonInit); +``` + +## 示例 2:线程内完整事务(Create -> Replace -> Print -> Delete) +```c +#include +#include +#include "RyanJson.h" + +static RyanJsonBool_e buildFreqObject(RyanJson_t *outFreqObj) +{ + RyanJson_t freqObj = RT_NULL; + + if (RT_NULL == outFreqObj) + { + return RyanJsonFalse; + } + + freqObj = RyanJsonCreateObject(); + if (RT_NULL == freqObj) + { + return RyanJsonFalse; + } + if (RyanJsonFalse == RyanJsonIsDetachedItem(freqObj)) + { + RyanJsonDelete(freqObj); + return RyanJsonFalse; + } + + if ((RyanJsonFalse == RyanJsonAddIntToObject(freqObj, "value", 100)) || + (RyanJsonFalse == RyanJsonAddStringToObject(freqObj, "unit", "Hz"))) + { + RyanJsonDelete(freqObj); + return RyanJsonFalse; + } + + *outFreqObj = freqObj; + return RyanJsonTrue; +} + +static void jsonWorkerEntry(void *parameter) +{ + RyanJson_t root = RyanJsonCreateObject(); + RyanJson_t freqObj = RT_NULL; + char outBuf[192]; + uint32_t outLen = 0U; + (void)parameter; + + if (RT_NULL == root) { return; } + + if ((RyanJsonFalse == RyanJsonAddStringToObject(root, "dev", "imu")) || + (RyanJsonFalse == RyanJsonAddIntToObject(root, "freq", 100))) + { + goto done; + } + + if (RyanJsonTrue == buildFreqObject(&freqObj)) + { + if (RyanJsonFalse == RyanJsonReplaceByKey(root, "freq", freqObj)) + { + RyanJsonDelete(freqObj); // Replace 失败:调用方负责释放 + } + } + + if (RT_NULL != RyanJsonPrintPreallocated(root, outBuf, (uint32_t)sizeof(outBuf), RyanJsonFalse, &outLen)) + { + rt_kprintf("json=%s\n", outBuf); + } + else + { + rt_kprintf("print failed\n"); + } + +done: + RyanJsonDelete(root); +} +``` + +## 示例 3:Detach 迁移子树 +```c +#include +#include "RyanJson.h" + +static RyanJsonBool_e detachMovePayload(RyanJson_t src, RyanJson_t dst) +{ + RyanJson_t detached = RT_NULL; + + if ((RT_NULL == src) || (RT_NULL == dst)) { return RyanJsonFalse; } + + detached = RyanJsonDetachByKey(src, "payload"); + if (RT_NULL == detached) { return RyanJsonFalse; } + + if (RyanJsonFalse == RyanJsonAddItemToObject(dst, "payload", detached)) + { + // 当前实现下,游离节点 AddItem 失败路径由库侧清理;不做二次释放 + return RyanJsonFalse; + } + + return RyanJsonTrue; +} +``` + +## 上板检查单(不依赖 unity/fuzzer) +1. hooks 初始化日志是否先于业务 Json 调用。 +2. Parse/Create/Add/Replace/Print 的返回值是否都处理。 +3. Replace 失败后是否没有泄漏 `newItem`。 +4. 事务退出点是否统一清理(`RyanJsonDelete`)。 +5. 异常输入是否返回 false/NULL 且不崩溃。 + +## 依据(仓库内) +- `RyanJson/RyanJson.c`:hooks 全局指针与 `RyanJsonInitHooks` +- `RyanJson/RyanJsonItem.c`:`RyanJsonAddItemToObject` / `RyanJsonReplaceByKey` / `RyanJsonDetachByKey` +- `RyanJson/RyanJsonPrint.c`:`RyanJsonPrintPreallocated` +- `test/unityTest/cases/core/testCreate.c`:AddItem/Insert 失败行为 +- `test/unityTest/cases/core/testReplace.c`:Replace 失败不消费新节点 +- `test/unityTest/cases/utils/testPrint.c`:预分配打印边界 diff --git a/skills/ryanjson-api-usage/references/terminology.md b/skills/ryanjson-api-usage/references/terminology.md new file mode 100644 index 0000000..4b9acf2 --- /dev/null +++ b/skills/ryanjson-api-usage/references/terminology.md @@ -0,0 +1,4 @@ +# 术语字典 + +- 统一术语定义复用共享文档:`../../shared/terminology.md`。 +- 如出现本技能专属术语,可在本文件追加扩展,不覆盖共享定义。 diff --git a/skills/ryanjson-api-usage/sop.md b/skills/ryanjson-api-usage/sop.md new file mode 100644 index 0000000..2ab3371 --- /dev/null +++ b/skills/ryanjson-api-usage/sop.md @@ -0,0 +1,30 @@ +# RyanJson API 工作流(压缩版) + +## 1. 先定边界 +1. 只使用公开 API,不展开内部实现细节。 +2. 先确认宏前提与 hooks 初始化方案。 +3. 明确用户目标类型:读取、构建、更新、替换、迁移。 + +## 2. 再给方案 +1. 选择最小 API 组合。 +2. 给出“成功 + 失败”双路径代码。 +3. 明确所有权转移与释放责任。 +4. 给出 RT-Thread 可执行验证点。 + +## 3. 最后交付 +1. 方案结论与前提。 +2. 最小代码。 +3. 所有权/释放清单。 +4. 验证步骤与扩展建议。 + +## 4. 常见漏项 +- 未初始化 hooks。 +- `Get*` 之前缺少判空/判型。 +- 把 Replace 失败语义误当 Add/Insert。 +- 未标注宏前提,导致预期错位。 + +## 5. 依据(仓库内) +- `RyanJson/RyanJson.c`:hooks 初始化与释放路径 +- `RyanJson/RyanJsonItem.c`:Insert/Replace 失败语义 +- `RyanJson/RyanJsonConfig.h`:宏前提(StrictKey / AddAtHead) +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c` diff --git a/skills/ryanjson-optimization/SKILL.md b/skills/ryanjson-optimization/SKILL.md new file mode 100644 index 0000000..53358c5 --- /dev/null +++ b/skills/ryanjson-optimization/SKILL.md @@ -0,0 +1,58 @@ +--- +name: ryanjson-optimization +description: 面向 RyanJson 核心代码的正确性优先优化技能。用于 Parse/Print/Item/Compare 优化、宏语义设计、内存与性能权衡、以及 crash/覆盖率回归闭环。用户请求“核心优化”“军工级稳定性”“最小内存提效”“不改 API 前提下优化”时使用本技能。 +--- + +# RyanJson 核心优化技能 + +## 技能定位 +- 面向核心实现层的“正确性优先”优化:Parse/Print/Item/Compare。 +- 目标是在不破坏契约的前提下提升性能、内存或稳定性。 + +## 输入要求 +- 明确优先级:性能、内存、稳定性、代码尺寸。 +- 明确边界:是否允许行为变化、是否允许新增宏、RAM/ROM 预算。 +- 明确门禁:必须通过的测试与覆盖回归范围。 + +## 必读入口 +- 共享基线:`../shared/ryanJsonCommon.md` +- 注释规范:统一使用 Doxygen 风格,且类型名/字段语义名/API 名保持英文(见共享基线第 9 节) +- 工作流:`references/coreWorkflow.md` +- 门禁:`references/regressionGates.md` +- 输出模板:`references/optimizationTemplate.md` + +## 执行入口 +- 代码规范:先执行 `bash ./run_local_format.sh --check --changed`,提交前执行 `bash ./run_local_format.sh`。 +- 本地常规回归:优先双链路 `run_local_*`(`run_local_base.sh` + `run_local_qemu.sh`)。 +- 特殊验证(矩阵细调/并发细调/覆盖率)再直调: + - `scripts/ci/runBaseCoverage.sh` + - `scripts/ci/runCoverage.sh` + +## 执行流程 +1. 先按 `../shared/ryanJsonCommon.md` 锁定宏前提和语义边界。 +2. 建立基线:正确性、耗时、内存峰值、崩溃样本、覆盖率。 +3. 设计最小改动:只改目标路径,避免顺手重构。 +4. 增量验证:每次改动先跑定向回归,再扩到全量门禁。 +5. 输出结论:收益、代价、兼容影响、剩余风险和回滚条件。 + +## 优化专项约束 +- 未授权不得改变公开 API 语义或默认行为。 +- 禁止引入依赖常驻额外节点开销的侵入式提速。 +- `Add/Insert/Replace` 失败语义必须与头文件和测试一致。 +- 宏语义变更必须同步代码、测试和文档。 +- 涉及对齐/异常路径时,必须补跑 QEMU 链路确认 `UNALIGN_TRP + HardFault` 行为。 + +## 输出格式 +1. 结论:优先级与目标达成情况(已验证/推断)。 +2. 方案:文件/函数级最小改动点与原因。 +3. 证据:unit、fuzz、覆盖、内存与性能数据。 +4. 风险:未覆盖边界、回滚触发条件、下一步建议。 + +## 参考导航 +- 核心结构:`references/coreArchitecture.md` +- 模块热点:`references/moduleHotspots.md` +- 配置开关:`references/configSwitches.md` +- 优化配方:`references/optimizationRecipes.md` +- 术语:`references/terminology.md` +- Gemini 对齐:`references/geminiCompat.md` +- 本地压缩文档:`architecture.md`、`pitfalls.md`、`sop.md` diff --git a/skills/ryanjson-optimization/agents/gemini.md b/skills/ryanjson-optimization/agents/gemini.md new file mode 100644 index 0000000..0588356 --- /dev/null +++ b/skills/ryanjson-optimization/agents/gemini.md @@ -0,0 +1,35 @@ +# Gemini Skill Card + +名称:`ryanjson-optimization` + +## 定位 +- 面向 RyanJson 核心代码的正确性优先优化技能。 + +## 适用场景 +- 优化 Parse/Print/Item/Compare 等核心路径。 +- 在嵌入式约束下平衡稳定性、性能、内存、代码尺寸。 +- 需要“先修正确性再提效”的工程化方案。 + +## 输入建议 +- 问题证据:崩溃日志、泄漏、覆盖率或性能瓶颈。 +- 约束条件:是否允许新增内存、是否允许行为变化。 +- 验收标准:需要通过的测试、覆盖、内存门禁。 + +## 硬约束 +- 正确性优先于性能,先修 P0/P1 风险再提效。 +- 保持公开 API 语义稳定,最小化改动范围。 +- 宏语义变更需同步代码、单测、fuzzer 与文档。 +- 语义不明确时按 `example/ -> test/unityTest/ -> test/fuzzer/` 取证。 + +## 术语口径 +- 统一按 `../references/terminology.md`。 +- 输出必须显式区分:已验证/推断、可恢复错误/不可恢复错误、失败语义。 + +## 默认提示词 +使用 `$ryanjson-optimization`,先给正确性风险分级,再给最小改动优化方案;在嵌入式 RAM/ROM 约束下保持 API 语义稳定,并基于 `example -> unityTest -> fuzzer` 取证给出可复现证据。 + +## 输出骨架 +1. 结论与风险分级(P0/P1/P2,标注已验证/推断)。 +2. 最小改动方案(文件 + 函数 + 原因 + 兼容影响)。 +3. 验证证据(unity/fuzzer/覆盖/内存)。 +4. 剩余风险、回滚条件、下一步建议。 diff --git a/skills/ryanjson-optimization/agents/openai.yaml b/skills/ryanjson-optimization/agents/openai.yaml new file mode 100644 index 0000000..486e7a5 --- /dev/null +++ b/skills/ryanjson-optimization/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "RyanJson 核心优化" + short_description: "正确性优先的核心优化与可复现回归证据" + default_prompt: "使用 $ryanjson-optimization 先修正确性再做性能/内存优化:保持公开 API 语义稳定,最小化改动范围,区分可恢复与不可恢复错误;语义不清时按 example -> unityTest -> fuzzer 取证并给出可复现证据。" diff --git a/skills/ryanjson-optimization/architecture.md b/skills/ryanjson-optimization/architecture.md new file mode 100644 index 0000000..2ad885d --- /dev/null +++ b/skills/ryanjson-optimization/architecture.md @@ -0,0 +1,32 @@ +# RyanJson 核心架构(压缩版) + +## 1. 作用 +- 提供优化前必须掌握的结构心智,避免“改对性能、改坏正确性”。 +- 详细结构说明见 `references/coreArchitecture.md`。 + +## 2. 节点与 Payload 心智 +- RyanJson 节点是紧凑模型,运行行为依赖内部负载布局。 +- 优化时不能按“传统大而全节点结构”做假设。 +- 未经授权不要引入会增加常驻节点开销的结构改动。 + +## 3. 链接与遍历约束 +- 结构编辑(`Detach/Replace/Delete`)必须保持遍历不变量。 +- 任何“简化链接重接”的改动都必须配套回归覆盖。 + +## 4. 字符串路径约束 +- 字符串处理存在短路径与动态路径切换。 +- 短长路径切换的更新逻辑必须保证无泄漏。 + +## 5. 分配器基线 +- 内部分配依赖 hooks;未对齐 hooks 的优化验证结果无效。 + +## 6. 优化守则 +1. 先正确再提速。 +2. 只做最小必要改动。 +3. 先验证失败路径,再验证热点路径。 + +## 7. 依据(仓库内) +- `RyanJson/RyanJson.c`:hooks 全局指针与初始化 +- `RyanJson/RyanJsonItem.c`:Detach/Replace/Delete 链与所有权路径 +- `RyanJson/RyanJsonConfig.h`:宏前提(StrictKey / AddAtHead) +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c` diff --git a/skills/ryanjson-optimization/pitfalls.md b/skills/ryanjson-optimization/pitfalls.md new file mode 100644 index 0000000..d42b143 --- /dev/null +++ b/skills/ryanjson-optimization/pitfalls.md @@ -0,0 +1,34 @@ +# RyanJson 优化雷区(压缩版) + +## 1. Parse 路径雷区 +- 非法输入下的数字/Unicode 边界最容易引发崩溃。 +- 回滚路径必须能清理“半构建”结构。 + +## 2. Print 路径雷区 +- 预分配输出路径对边界计数极其敏感。 +- 终止符与长度边界要重点防 off-by-one。 + +## 3. 所有权雷区 +- `Add/Insert` 与 `Replace` 失败语义不同。 +- 错把两者当同一规则会导致泄漏或双重释放。 + +## 4. Compare/遍历雷区 +- 大对象比较会进入高成本路径。 +- 改动比较逻辑必须覆盖深层与乱序结构回归。 + +## 5. 宏相关雷区 +- `RyanJsonDefaultAddAtHead` 会影响追加顺序与索引语义。 +- `RyanJsonStrictObjectKeyCheck` 会影响重复 key 预期。 + +## 6. 审查检查单 +1. 是否改变了公开语义。 +2. 是否覆盖了失败路径。 +3. 宏敏感断言是否同步。 +4. 相关模块是否复跑 fuzz 回归。 + +## 7. 依据(仓库内) +- `RyanJson/RyanJsonParse.c`:Parse 回滚与尾部语义 +- `RyanJson/RyanJsonPrint.c`:预分配边界与失败路径 +- `RyanJson/RyanJsonItem.c`:Add/Insert/Replace 失败语义差异 +- `test/fuzzer/entry.c`:fuzzer 入口与稳定性回归链路 +- `../shared/ryanJsonCommon.md`:统一执行入口与覆盖约定 diff --git a/skills/ryanjson-optimization/references/configSwitches.md b/skills/ryanjson-optimization/references/configSwitches.md new file mode 100644 index 0000000..2e8f047 --- /dev/null +++ b/skills/ryanjson-optimization/references/configSwitches.md @@ -0,0 +1,40 @@ +# 配置宏设计规范 + +## 范围 +- 本页只讲“宏语义设计”和“宏变更验收点”。 +- 完整优化任务交付骨架见 `optimizationTemplate.md`。 + +## 宏新增/修改基本规则 +- 只为“可明确切换的行为”加宏,不为临时调试加宏。 +- 宏默认值优先兼容历史行为。 +- 注释必须写清:启用后收益、代价、兼容影响、测试影响。 + +## 变更流程 +1. 定义宏语义和默认值。 +2. 在 `RyanJsonConfig.h` 写清行为差异。 +3. 同步核心代码分支。 +4. 同步 unity + fuzzer 条件断言。 +5. 在变更说明中列出风险与回滚条件。 + +## 典型宏:`RyanJsonStrictObjectKeyCheck` +- `true`:拒绝重复 key,输入更严格,早发现脏数据。 +- `false`:允许重复 key,通常首匹配,语义依赖调用方约束。 + +## 宏级别验收清单 +- Parse 是否符合宏期望。 +- Add/Insert/Replace 是否符合宏期望。 +- Compare/读取行为是否在文档中声明。 +- fuzzer 断言是否按宏分支。 + +## 提交说明模板 +- 变更宏: +- 默认行为变化: +- 受影响 API: +- 测试覆盖: +- 兼容风险: +- 回滚策略: + +## 依据(仓库内) +- `RyanJson/RyanJsonConfig.h`:严格 key / AddAtHead 宏定义与默认值 +- `RyanJson/RyanJson.h`:宏影响的公开 API 语义注释 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`:宏分支断言 diff --git a/skills/ryanjson-optimization/references/coreArchitecture.md b/skills/ryanjson-optimization/references/coreArchitecture.md new file mode 100644 index 0000000..ec4bc66 --- /dev/null +++ b/skills/ryanjson-optimization/references/coreArchitecture.md @@ -0,0 +1,74 @@ +# RyanJson 核心架构速图(优化视角) + +## 范围 +- 本页说明优化决策需要的结构心智,不包含具体改动步骤。 +- 执行流程见 `coreWorkflow.md`,门禁见 `regressionGates.md`。 + +## 1. 数据结构基线 +- `RyanJson_t` = `struct RyanJsonNode *`。 +- `struct RyanJsonNode` 公开只有 `next` 字段;其余元数据与值在 payload 区。 +- payload 起始 1 字节是 flag,随后是类型相关数据区。 + +## 2. 节点分配模型(`RyanJsonInternalNewNode`) +分配大小 = 基础头 + 类型值区 + 可选 inline 区: +- 基础:`sizeof(node) + 1B flag` +- number:+4 或 +8 +- array/object:+`sizeof(RyanJson_t)`(children 指针) +- 有 key 或类型是 string:+`RyanJsonInlineStringSize` + +优化含义: +- 只有“可能存 key/string”的节点才有 inline 区。 +- object/array 的 children 指针在 payload 区,不在 struct 字段里。 + +## 3. 字符串存储模式 +- inline:短 key/value 放在节点内,减少堆分配。 +- ptr:长字符串放外部堆块,payload 存指针。 +- `RyanJsonInternalChangeString` 负责模式切换和旧堆块释放。 + +优化红线: +- 不能破坏模式切换后的所有权与释放顺序。 + +## 4. 线索化链(优化常踩坑) +- `IsLast=0`:`next` 指向兄弟。 +- `IsLast=1`:`next` 指向父节点(不是兄弟)。 + +影响: +- `RyanJsonGetNext` 会屏蔽 `IsLast` 并返回 NULL。 +- 内部遍历/删除经常直接用 `node->next` 做回溯。 +- 优化遍历逻辑时,不能把 `next` 一律当兄弟指针。 + +## 5. Delete/Duplicate 都是非递归 +- `RyanJsonDelete`:下沉子树,叶子释放后通过线索回溯。 +- `RyanJsonDuplicate`:同步遍历源树与目标树,依赖 `IsLast` 维护层级。 + +优化建议: +- 若要改遍历路径,优先补深层结构回归和栈风险回归。 + +## 6. Hooks 是全局运行前置 +- `jsonMalloc/jsonFree/jsonRealloc` 为全局函数指针。 +- 必须由 `RyanJsonInitHooks` 先初始化。 +- 优化结论必须在 hooks 已初始化条件下给出(否则数据不可比)。 +- 当前实现中 hooks 默认值为 `NULL`,未初始化状态不可作为有效基线。 + +## 7. 对外/对内 API 边界 +- 对外 API 用 `RyanJsonXxx`(头文件公开声明)。 +- 跨文件内部 API 用 `RyanJsonInternalApi` + `RyanJsonInternalXxx`。 +- 优化时不要把内部辅助函数误暴露为公开契约。 + +## 8. 配置宏对行为的影响 +- `RyanJsonStrictObjectKeyCheck` 改变 parse/insert/replace 的重复 key 行为。 +- `RyanJsonInlineStringSize`、`RyanJsonMallocHeaderSize`、`RyanJsonMallocAlign` 影响节点内存布局边界。 + +优化动作前,先锁定这些宏值。 + +## 9. 优化取证最小顺序 +1. 头文件语义(`RyanJson.h` / `RyanJsonConfig.h`) +2. 核心实现(`RyanJson*.c`) +3. 样例与测试(`example/` -> `test/unityTest/` -> `test/fuzzer/`) + +若三者冲突,先修文档和测试,再收敛实现。 + +## 10. 关键依据(仓库内) +- `RyanJson/RyanJson.c`:hooks 全局变量与 `RyanJsonInitHooks` +- `RyanJson/RyanJsonUtils.c`、`RyanJson/RyanJsonParse.c`、`RyanJson/RyanJsonPrint.c`:`jsonMalloc/jsonFree` 调用路径 +- `test/unityTest/common/testCommon.c`、`test/fuzzer/entry.c`:测试入口 hooks 初始化 diff --git a/skills/ryanjson-optimization/references/coreWorkflow.md b/skills/ryanjson-optimization/references/coreWorkflow.md new file mode 100644 index 0000000..154ef83 --- /dev/null +++ b/skills/ryanjson-optimization/references/coreWorkflow.md @@ -0,0 +1,61 @@ +# RyanJson 核心优化工作流 + +## 范围 +- 本页仅覆盖“优化执行流程”。 +- 交付结构见 `optimizationTemplate.md`。 +- 模块风险细节见 `moduleHotspots.md`。 +- 执行入口、覆盖目录、脚本参数基线见 `../../shared/ryanJsonCommon.md`。 + +## 0) 前置条件(必须) +- 在任何业务入口、测试入口、fuzzer 入口先调用 `RyanJsonInitHooks`。 +- 默认建议使用统一封装分配器(即便最终仍转发到 `malloc/free/realloc`),便于统计和故障注入。 +- 未初始化 hooks 时,不执行优化结论对比(避免基线不可比)。 +- 当前实现没有默认 hooks 回退(全局 hooks 初始为 `NULL`),因此该前置条件是硬约束。 + +## 1) 定义问题边界 +- 明确是“行为错误修复”还是“性能/内存优化”。 +- 明确是否允许改变历史行为(默认不允许)。 +- 明确目标平台(32 位/64 位、是否 RTOS、是否启用 ASan/UBSan)。 + +## 2) 建立可比较基线 +- 功能:跑与改动模块相关的单元测试。 +- 资源:记录内存峰值、分配次数、失败路径泄漏结果。 +- 稳定性:保留至少一个历史 crash 样本回归。 +- 覆盖:确认目标分支 true/false 双向是否可达。 + +## 2.1) 回归入口策略 +- 本地常规回归:先跑 `run_local_*`。 +- 需要细调矩阵/并发/覆盖时,再直调 `scripts/ci/*`。 + +## 3) 聚焦热点模块 +- `RyanJsonParse.c`:数字解析、字符串转义、Unicode 代理对、状态推进。 +- `RyanJsonPrint.c`:预分配边界、append 失败路径、double 打印策略。 +- `RyanJsonItem.c`:Add/Insert/Replace/Detach/Delete 的所有权与链安全。 +- `RyanJson.c`:Compare 有序/无序路径、深度与回溯。 + +## 4) 设计最小改动 +- 优先修复内存安全与语义一致性,再做微优化。 +- 新防御逻辑要区分可恢复错误(返回 false/NULL)与不可恢复错误(启用 `RyanJsonEnableAssert` 时可 assert)。 +- 避免引入额外常驻内存;若必须引入,先得到明确授权。 + +## 5) 同步测试策略 +- 不新增散文件,补到现有分类测试。 +- 单元测试覆盖确定性语义,fuzzer 覆盖组合路径与未知输入。 +- 新增每个分支至少一个触发样本,必要时固化到 corpus。 + +## 6) 交付与复核 +- 给出改动原因、收益、代价、兼容影响。 +- 给出“已验证/未验证”边界。 +- 给出剩余风险和下一轮优化建议。 + +## 常见反模式 +- 只看覆盖率,不看语义断言。 +- 通过 assert 处理用户输入错误。 +- 为了覆盖率引入会改变行为的冗余逻辑。 + +## 依据(仓库内) +- `RyanJson/RyanJson.c`:`RyanJsonInitHooks` 与全局 hooks 初始状态 +- `RyanJson/RyanJson.h`:`Add/Insert`、`Replace` 失败语义注释 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`:失败所有权与宏分支断言 +- `test/fuzzer/entry.c`:fuzzer 入口与稳定性回归链路 +- `../../shared/ryanJsonCommon.md`:统一执行入口与覆盖口径 diff --git a/skills/ryanjson-optimization/references/geminiCompat.md b/skills/ryanjson-optimization/references/geminiCompat.md new file mode 100644 index 0000000..6a1a84d --- /dev/null +++ b/skills/ryanjson-optimization/references/geminiCompat.md @@ -0,0 +1,32 @@ +# Gemini 兼容说明(优化类任务) + +## 范围 +- 本页只定义 Gemini 在优化任务中的输入/输出结构。 +- 优化流程与门禁口径以 `coreWorkflow.md`、`regressionGates.md` 为准。 + +## 推荐输入模板 +- 背景:当前失败日志或性能瓶颈。 +- 目标:先修正确性/再做性能 或 先性能/再验证。 +- 约束:不新增额外内存、不改 API、测试文件归档规则。 +- 验收:需要通过的测试与覆盖率证据。 + +## 推荐输出结构 +1. 风险分级(P0/P1/P2)。 +2. 最小改动方案(文件 + 函数 + 预期行为)。 +3. 验证步骤(命令 + 预期)。 +4. 残余风险与下一步。 + +字段组织可直接复用 `optimizationTemplate.md`。 + +## 禁止输出模式 +- 只给“建议”不给落地步骤。 +- 忽略失败路径与释放语义。 +- 不区分“已验证”与“推断”。 + +## 跨模型对齐真值 +- API 与头文件注释优先于模型记忆。 +- 覆盖率必须看分支双向触达。 +- assert 只能用于不可恢复内部破坏场景。 + +## 术语字典(统一) +- 本文术语统一以 `terminology.md` 为准。 diff --git a/skills/ryanjson-optimization/references/moduleHotspots.md b/skills/ryanjson-optimization/references/moduleHotspots.md new file mode 100644 index 0000000..e8bcf2b --- /dev/null +++ b/skills/ryanjson-optimization/references/moduleHotspots.md @@ -0,0 +1,44 @@ +# RyanJson 模块热点与审查清单 + +## 范围 +- 本页只列“风险热点”和“审查项”。 +- 改动步骤见 `coreWorkflow.md`。 +- 输出结构见 `optimizationTemplate.md`。 + +## RyanJsonParse.c(高风险) +- 数字路径:整数、小数、指数累计时的溢出保护。 +- 状态推进:`RyanJsonParseBufTryAdvanceCurrentPtr` 的成功/失败路径是否一致。 +- 字符串路径:转义、Unicode 解码、代理对有效性。 +- 错误回滚:失败后当前节点与解析状态是否残留脏数据。 + +## RyanJsonPrint.c(高风险) +- 预分配模式:长度刚好够用时的边界是否判定正确。 +- append 失败:是否释放内部缓冲、是否返回 NULL。 +- double 打印:`isinf`/`isnan` 的 RFC8259 策略是否一致。 +- style/preset:用户自定义样式与默认行为是否冲突。 + +## RyanJsonItem.c(高风险) +- 所有权语义:分别核对 Add/Insert 与 Replace 的失败路径是否符合既定契约(不可混用)。 +- 游离节点保护:`RyanJsonIsDetachedItem` 防御分支是否覆盖。 +- 重复 key 语义:严格/非严格模式行为与注释一致。 +- 链完整性:next/last 标志在插入、替换、分离后的一致性。 + +## RyanJson.c Compare(高风险) +- 同序快路径:是否会误判 key 对齐。 +- 乱序路径:对象查找是否稳定、数组比较是否出现回溯爆炸。 +- 深度与栈:深层嵌套下是否触发栈风险。 + +## RyanJsonInternal.h / 跨文件内部 API(中高风险) +- 仅内部接口使用 `RyanJsonInternalApi` + `RyanJsonInternalXxx` 命名。 +- 禁止把内部函数直接暴露为公开 API。 +- 跨模块重命名后要同步 callsite、注释与技能文档。 + +## RyanJsonConfig.h(中高风险) +- 宏新增必须写“收益/代价/兼容影响”。 +- 默认值必须兼容历史行为。 +- 任何宏语义变化都要同步 unity/fuzzer 预期。 + +## 依据(仓库内) +- `RyanJson/RyanJsonItem.c`:`RyanJsonInsert`、`RyanJsonAddItemToObject`、`RyanJsonReplaceByKey/ByIndex` +- `RyanJson/RyanJsonConfig.h`:`RyanJsonStrictObjectKeyCheck`、`RyanJsonDefaultAddAtHead` +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c` diff --git a/skills/ryanjson-optimization/references/optimizationRecipes.md b/skills/ryanjson-optimization/references/optimizationRecipes.md new file mode 100644 index 0000000..ad388c9 --- /dev/null +++ b/skills/ryanjson-optimization/references/optimizationRecipes.md @@ -0,0 +1,64 @@ +# 优化策略库(可直接复用) + +## 范围 +- 本页只保留“可复用配方”。 +- 执行顺序见 `coreWorkflow.md`。 +- 交付结构见 `optimizationTemplate.md`。 + +## 配方 1:先修语义再提速 +适用:崩溃、泄漏、误判与性能问题同时存在。 + +步骤: +1. 先修内存安全/语义一致性。 +2. 用最小输入补单测回归。 +3. 再做性能微调(减少重复查找、减少无效分支)。 + +## 配方 2:零额外内存优化 +适用:用户明确“不能增加额外内存”。 + +优先手段: +- 减少重复计算与重复遍历。 +- 改善分支顺序,提高常见路径命中。 +- 缩短失败路径,减少临时对象生命周期。 + +避免: +- 引入缓存表或哈希索引(除非授权)。 + +## 配方 3:Compare 逻辑优化 +适用:对象比较高频、深层结构较多。 + +优先检查: +- 同序快路径是否正确。 +- 乱序路径是否 fallback 正确。 +- 深度上限和回溯开销是否可控。 + +## 配方 4:Print 失败路径稳健化 +适用:预分配失败、append 失败、double 特殊值场景。 + +要点: +- 刚好够用边界(`<=`)必须有单测。 +- 内部缓冲失败必须释放并返回 NULL。 +- `inf/nan` 输出策略需与规范一致并有断言。 + +## 配方 5:Parse 数值边界防护 +适用:覆盖指数溢出、超长小数、非法格式。 + +要点: +- 分阶段防溢出(整数、小数、指数分别保护)。 +- 避免前置判断吞掉目标分支。 +- 用单测 + fuzz 双路径验证。 + +## 当前项目冻结项(默认不改) +以下是当前已确认的项目决策,除非用户明确要求,否则不要主动推动改动: +- `RyanJsonParse` 默认保持非严格尾部语义(不改为 strict default)。 +- Compare 相关“纯性能优化”(不改变行为)当前优先级低,默认只做正确性修复。 +- 字符串 `\uXXXX` 预扫长度的进一步内存微优化(更复杂的精算)当前不做。 + +补充约束: +- Compare 的结构破坏路径按“内部不变量损坏”处理;启用 `RyanJsonEnableAssert` 时可走 assert。 + +## 依据(仓库内) +- `RyanJson/RyanJsonParse.c`:Parse 默认尾部语义与数值路径 +- `RyanJson/RyanJsonPrint.c`、`test/unityTest/cases/utils/testPrint.c`:Print 预分配边界与失败路径 +- `RyanJson/RyanJson.c`:Minify 行为与 Compare 入口 +- `test/unityTest/cases/core/testReplace.c`、`test/unityTest/cases/core/testCreate.c`:核心失败语义回归 diff --git a/skills/ryanjson-optimization/references/optimizationTemplate.md b/skills/ryanjson-optimization/references/optimizationTemplate.md new file mode 100644 index 0000000..b3cbedc --- /dev/null +++ b/skills/ryanjson-optimization/references/optimizationTemplate.md @@ -0,0 +1,34 @@ +# 优化任务模板(复用) + +## 范围 +- 适用于 RyanJson 核心优化任务的统一交付结构。 +- 本模板负责“输入/方案/验证/风险”骨架;模块细节见其他 references。 +- 执行入口与覆盖口径见 `../../shared/ryanJsonCommon.md`。 + +## 1) 输入快照 +- 目标: +- 约束(RAM/ROM/是否可新增内存): +- 兼容要求(是否允许行为变化): +- 验收标准(测试/覆盖/泄漏): + +## 2) 风险分级(P0/P1/P2) +- P0: +- P1: +- P2: + +## 3) 最小改动方案 +- 文件: +- 函数: +- 改动点: +- 兼容影响: + +## 4) 验证证据 +- 已执行验证: +- 结果摘要: +- 失败项与原因: + +## 5) 结论与后续 +- 结论(已验证/推断): +- 剩余风险: +- 回滚条件: +- 下一步建议: diff --git a/skills/ryanjson-optimization/references/regressionGates.md b/skills/ryanjson-optimization/references/regressionGates.md new file mode 100644 index 0000000..08b31ab --- /dev/null +++ b/skills/ryanjson-optimization/references/regressionGates.md @@ -0,0 +1,42 @@ +# 回归门禁与验收模板 + +## 范围 +- 本页只定义门禁标准与阻塞条件。 +- 任务结论写法见 `optimizationTemplate.md`。 +- 执行入口与脚本参数基线见 `../../shared/ryanJsonCommon.md`。 + +## 门禁级别 +- Gate-0:改动模块相关单测全部通过。 +- Gate-1:全量 unity 通过且无内存泄漏。 +- Gate-2:fuzzer 冒烟通过(含历史 crash 样本)。 +- Gate-3:关键覆盖分支双向触达。 + +## 执行入口约定 +- 本地常规门禁:优先 `run_local_*`。 +- 特殊门禁场景(覆盖率、矩阵细调、并发细调)再直调 `scripts/ci/*`。 + +## 必验项清单 +- Parse:非法输入返回 NULL,无崩溃。 +- Item:Add/Insert/Replace/Detach/Delete 所有权一致。 +- Print:动态与预分配模式行为一致。 +- Compare:有序/乱序路径符合预期。 +- 宏开关:严格/非严格语义与文档一致。 + +## 结果记录模板 +- 改动范围: +- 通过的门禁: +- 失败的门禁: +- 是否阻塞合入: +- 临时豁免原因: +- 后续修复计划: + +## 回滚触发条件 +- 出现 P0 崩溃或内存破坏。 +- 语义回归影响现有 API 合同。 +- 无法在限制周期内补齐测试证据。 + +## 依据(仓库内) +- `test/unityTest/runner/main.c`、`test/unityTest/common/testCommon.c`:unity 入口与泄漏检测门禁 +- `test/fuzzer/entry.c`:fuzzer 冒烟与回归链路 +- `../../shared/ryanJsonCommon.md`:统一执行入口与覆盖口径 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`:Item 所有权回归基础用例 diff --git a/skills/ryanjson-optimization/references/terminology.md b/skills/ryanjson-optimization/references/terminology.md new file mode 100644 index 0000000..4b9acf2 --- /dev/null +++ b/skills/ryanjson-optimization/references/terminology.md @@ -0,0 +1,4 @@ +# 术语字典 + +- 统一术语定义复用共享文档:`../../shared/terminology.md`。 +- 如出现本技能专属术语,可在本文件追加扩展,不覆盖共享定义。 diff --git a/skills/ryanjson-optimization/sop.md b/skills/ryanjson-optimization/sop.md new file mode 100644 index 0000000..3391c5f --- /dev/null +++ b/skills/ryanjson-optimization/sop.md @@ -0,0 +1,31 @@ +# RyanJson 优化工作流(压缩版) + +## 1. 先建基线 +1. 用当前代码复现问题(正确性/性能/内存)。 +2. 采集基线证据(行为+指标)。 +3. 先定义验收标准再开始改动。 + +## 2. 实施循环 +1. 做最小局部改动。 +2. 先跑定向回归。 +3. 再校验失败路径和宏敏感行为。 +4. 迭代到目标达成为止。 + +## 3. 回归顺序 +1. 本地常规先跑 `run_local_*`。 +2. 需要细调矩阵/并发/覆盖时,再直调 `scripts/ci/*`。 +3. 最终门禁覆盖 unit + fuzz + 历史崩溃样本。 + +## 4. 交付结构 +1. 改了什么,为什么改。 +2. 收益与代价。 +3. 已验证证据与剩余风险。 +4. 回滚条件与后续建议。 + +## 5. 依据(仓库内) +- `xmake.lua`:`RyanJson` / `RyanJsonFuzz` 模式 target +- `run_local_base.sh`、`run_local_ci.sh`、`run_local_fuzz.sh`:本地常规入口 +- `scripts/ci/runBaseCoverage.sh`:unit 特殊矩阵/覆盖执行链 +- `scripts/ci/runCoverage.sh`:fuzz 特殊参数/覆盖执行链 +- `RyanJson/RyanJsonConfig.h`:宏前提 +- `test/unityTest/runner/main.c`:unit 入口模式隔离 diff --git a/skills/ryanjson-test-engineering/SKILL.md b/skills/ryanjson-test-engineering/SKILL.md new file mode 100644 index 0000000..deeeb74 --- /dev/null +++ b/skills/ryanjson-test-engineering/SKILL.md @@ -0,0 +1,56 @@ +--- +name: ryanjson-test-engineering +description: 面向 RyanJson 单元测试与模糊测试的覆盖工程技能。用于按现有分类补充边界/失败路径/所有权断言、修复泄漏与崩溃回归、并提升关键分支触达质量。用户请求“扩展单测”“补覆盖率”“fuzzer 崩溃回归”时使用本技能。 +--- + +# RyanJson 测试工程技能 + +## 技能定位 +- 面向 unit/fuzz 的测试补强与回归闭环。 +- 重点是失败路径、边界条件、所有权语义和覆盖率触达质量。 + +## 必读入口 +- 共享基线:`../shared/ryanJsonCommon.md` +- 注释规范:统一使用 Doxygen 风格,且类型名/字段语义名/API 名保持英文(见共享基线第 9 节) +- 断言与分层:`references/unityPlaybook.md`、`references/fuzzerPlaybook.md` +- 分诊与回归:`references/coverageTriage.md`、`references/regressionMatrix.md` +- 输出模板:`references/testcaseTemplate.md` + +## 执行入口 +- 代码规范:先执行 `bash ./run_local_format.sh --check --changed`,提交前执行 `bash ./run_local_format.sh`。 +- 本地常规回归:优先双链路 `run_local_*`。 + - `run_local_base.sh`:默认 full unit(跳过覆盖) + - `run_local_qemu.sh`:默认 full QEMU 矩阵(完整 localbase 单测 + 非对齐 fault 校验) + - `run_local_ci.sh`:默认 full unit + quick fuzz + - `run_local_fuzz.sh`:默认 6 并发 + 固定轮次 +- 特殊测试再直调: + - unit:`scripts/ci/runBaseCoverage.sh` + - fuzz:`scripts/ci/runCoverage.sh` + +## 执行流程 +1. 先按 `../shared/ryanJsonCommon.md` 确认宏前提和模式边界。 +2. 从日志判定问题类型:崩溃、泄漏、断言失败、分支未命中。 +3. 先补最小确定性 unit 用例,再补 fuzz 触达样本。 +4. 回归时先走本地入口,必要时用 `scripts/ci/*` 细调参数。 +5. 输出证据:分支触达、泄漏状态、崩溃状态、覆盖率路径。 + +## 测试专项约束 +- 不新建零散文件,优先补到现有分类。 +- unit/fuzz 必须隔离,禁止把两种模式写成同一条混合建议。 +- `Add/Insert` 与 `Replace` 的失败所有权必须分开断言。 +- 覆盖率目录为固定路径且每次会清理旧结果,不假设保留历史目录。 +- QEMU 链路不再有 basic scope;默认与 localbase 用例集一致(RFC8259 仍留 Linux 快速链路)。 +- RFC8259 文件集通过目录扫描(`readdir`)获取,不依赖 `rfc8259_filelist.inc`。 + +## 输出格式 +1. 问题与目标:失败现象 + 目标分支。 +2. 改动清单:文件、用例函数、场景意图。 +3. 验证证据:执行模式、命中分支、泄漏/崩溃结果、覆盖率产物。 +4. 风险与下一步:未覆盖边界与后续补测建议。 + +## 参考导航 +- 架构检查点:`references/coreArchitectureCheckpoints.md` +- 断言策略:`references/assertPolicy.md` +- 术语:`references/terminology.md` +- Gemini 对齐:`references/geminiCompat.md` +- 本地压缩文档:`testArchitecture.md`、`context.md`、`sop.md` diff --git a/skills/ryanjson-test-engineering/agents/gemini.md b/skills/ryanjson-test-engineering/agents/gemini.md new file mode 100644 index 0000000..83a23bf --- /dev/null +++ b/skills/ryanjson-test-engineering/agents/gemini.md @@ -0,0 +1,35 @@ +# Gemini Skill Card + +名称:`ryanjson-test-engineering` + +## 定位 +- 面向 RyanJson 单测与模糊测试的覆盖率和稳定性工程技能。 + +## 适用场景 +- 扩展单元测试与模糊测试的边界覆盖。 +- 修复泄漏、崩溃、断言误用与失败路径缺失。 +- 覆盖率报告出现关键分支未触达。 + +## 输入建议 +- 失败证据:报错日志、未覆盖分支、崩溃样本。 +- 约束条件:是否禁止新建文件、宏配置、内存限制。 +- 验收标准:目标分支、泄漏标准、回归命令。 + +## 硬约束 +- 默认不新建测试文件,优先补到现有分类。 +- 断言语义必须区分 Add/Insert 与 Replace 的失败所有权规则。 +- 不用业务 assert 代替可恢复错误断言。 +- 语义不明确时按 `example/ -> test/unityTest/ -> test/fuzzer/` 取证。 + +## 术语口径 +- 统一按 `../references/terminology.md`。 +- 输出必须显式区分:已验证/推断、可恢复错误/不可恢复错误、失败语义。 + +## 默认提示词 +使用 `$ryanjson-test-engineering`,在现有测试分类中扩展 RyanJson 单测与 fuzzer:优先覆盖失败路径和边界分支,明确所有权断言,并基于 `example -> unityTest -> fuzzer` 取证提供可复现回归证据。 + +## 输出骨架 +1. 失败根因与目标分支(标注已验证/推断)。 +2. 测试改动清单(文件 + 函数 + 场景)。 +3. 覆盖与稳定性证据(分支触达/泄漏/崩溃)。 +4. 回归边界与下一步建议。 diff --git a/skills/ryanjson-test-engineering/agents/openai.yaml b/skills/ryanjson-test-engineering/agents/openai.yaml new file mode 100644 index 0000000..269591f --- /dev/null +++ b/skills/ryanjson-test-engineering/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "RyanJson 测试工程" + short_description: "基于现有分类的 unit/fuzz 覆盖补强与所有权断言" + default_prompt: "使用 $ryanjson-test-engineering 在现有分类中扩展 RyanJson 单测与模糊测试:优先补失败路径、边界分支和所有权断言(区分 Add/Insert 与 Replace 失败语义);语义不清时按 example -> unityTest -> fuzzer 取证并给出回归证据。" diff --git a/skills/ryanjson-test-engineering/context.md b/skills/ryanjson-test-engineering/context.md new file mode 100644 index 0000000..563fae1 --- /dev/null +++ b/skills/ryanjson-test-engineering/context.md @@ -0,0 +1,32 @@ +# RyanJson 测试语境(压缩版) + +## 1. 作用 +- 统一测试侧断言边界与宏敏感规则,减少假阳性。 + +## 2. 测试前必查 +1. 当前宏值: + - `RyanJsonStrictObjectKeyCheck` + - `RyanJsonDefaultAddAtHead` +2. 当前模式:unit 或 fuzz。 +3. 执行入口与覆盖目录口径:见 `../shared/ryanJsonCommon.md`。 + +## 3. 断言边界 +- 业务/输入错误:断言可恢复结果(false/NULL)。 +- 内存破坏/不变量损坏:可视为致命行为;启用 `RyanJsonEnableAssert` 时可进入 assert 路径。 + +## 4. 高风险覆盖区 +- Parse 失败回滚。 +- Replace 失败所有权清理。 +- AddAtHead 相关索引断言。 +- 字符串边界切换。 + +## 5. 质量检查单 +1. true/false 分支是否双向覆盖。 +2. 失败分支是否断言了所有权。 +3. 测试说明是否标明模式前提。 + +## 6. 依据(仓库内) +- `xmake.lua`:`RyanJson`(unit)与 `RyanJsonFuzz`(fuzz)目标分离 +- `test/unityTest/runner/main.c`:`#ifndef isEnableFuzzer` 的 unit `main` 路径 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`:失败分支所有权断言 +- `../shared/ryanJsonCommon.md`:统一执行入口与覆盖口径 diff --git a/skills/ryanjson-test-engineering/references/assertPolicy.md b/skills/ryanjson-test-engineering/references/assertPolicy.md new file mode 100644 index 0000000..c36e42d --- /dev/null +++ b/skills/ryanjson-test-engineering/references/assertPolicy.md @@ -0,0 +1,25 @@ +# Assert 使用策略 + +## 范围 +- 仅用于不可恢复的内部不变量破坏。 +- 不用于用户可构造输入导致的常规错误。 +- 术语口径见 `../../shared/terminology.md`。 + +## 判定规则 +- 输入错误可恢复:返回 false/NULL,并保证资源回收。 +- 内存破坏或结构破坏不可恢复:允许 assert(启用 `RyanJsonEnableAssert` 时)。 +- Compare 内部对象链/Key 不变量属于结构破坏范畴,可按 assert 路径处理,不要求业务输入可达覆盖。 + +## 测试策略 +- 单元测试验证“可恢复错误”返回路径。 +- fuzzer 避免把业务预期写成 assert。 +- 对 assert 路径,重点确认是否误分类。 + +## 常见误用 +- 用 assert 拦截重复 key、非法数字、非法字符串输入。 +- 以 assert 代替失败路径释放逻辑。 + +## 依据(仓库内) +- `RyanJson/RyanJson.h`:断言宏与公开语义注释 +- `RyanJson/RyanJsonItem.c`:失败路径与可恢复错误返回 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c` diff --git a/skills/ryanjson-test-engineering/references/coreArchitectureCheckpoints.md b/skills/ryanjson-test-engineering/references/coreArchitectureCheckpoints.md new file mode 100644 index 0000000..838c44e --- /dev/null +++ b/skills/ryanjson-test-engineering/references/coreArchitectureCheckpoints.md @@ -0,0 +1,62 @@ +# 核心架构测试检查点 + +## 范围 +- 把 RyanJson 核心结构映射为“必须覆盖的测试断言点”。 +- 本页强调“测什么”,执行与覆盖入口见 `../../shared/ryanJsonCommon.md`。 + +## 1. `RyanJson_t` / payload 布局相关 +检查点: +- 类型读取是否与 flag 一致(Null/Bool/Number/String/Array/Object)。 +- number 的 int/double 分支都要有断言。 +- object/array 的 children 指针读取应只在容器类型下进行。 + +建议: +- 使用前必须使用 `RyanJsonIsXXX` 判断类型。 + +## 2. 字符串 inline/ptr 模式切换 +检查点: +- 短字符串走 inline,长字符串走 ptr。 +- ChangeString 后模式切换时不泄漏旧堆块。 +- key/value 边界长度(刚好临界值)必须有测试。 + +建议: +- 单测做临界值;fuzzer 做随机长度扰动。 + +## 3. 线索化链结构(IsLast + next) +检查点: +- 尾节点 `IsLast=1` 时 `RyanJsonGetNext` 返回 NULL。 +- 内部操作后链是否保持可遍历(Insert/Replace/Detach/Delete 组合)。 +- 错误使用非游离节点插入时能否被防御性拒绝。 + +建议: +- 多层对象+数组混合结构下验证 Delete 无崩溃无泄漏。 + +## 4. 所有权与释放语义 +检查点: +- Create 后未挂树节点是否可安全释放。 +- Add/Insert/Replace 成功后所有权转移是否成立。 +- Detach 返回节点是否需要调用者释放。 +- Add/Insert 失败时:游离 item 由库侧清理,非游离 item 不释放。 +- Replace 失败时:item 仍由调用者持有(可复用/可释放)。 + +建议: +- 每个接口至少 1 个“失败后内存计数归零”用例。 + +## 5. hooks 前置条件 +检查点: +- hooks 初始化前调用核心 API 的行为(当前实现无默认 hooks,测试侧应视为禁止路径)。 +- hooks 初始化失败是否被正确处理。 +- OOM 注入下失败路径是否可恢复且无泄漏。 + +## 6. 语义不明确时的取证顺序 +1. `example/` +2. `test/unityTest/` +3. `test/fuzzer/` + +若仍不明确,回到头文件注释与核心实现,避免靠经验推断。 + +## 依据(仓库内) +- `RyanJson/RyanJson.h`:类型判断公开接口 `RyanJsonIsNull/Bool/Number/...` +- `RyanJson/RyanJson.c`:hooks 全局指针初始化与 `RyanJsonInitHooks` +- `test/unityTest/common/testCommon.c`:测试入口 hooks 初始化 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`:所有权失败语义断言 diff --git a/skills/ryanjson-test-engineering/references/coverageTriage.md b/skills/ryanjson-test-engineering/references/coverageTriage.md new file mode 100644 index 0000000..568a6a8 --- /dev/null +++ b/skills/ryanjson-test-engineering/references/coverageTriage.md @@ -0,0 +1,41 @@ +# 覆盖率与回归分诊 + +## 范围 +- 本页仅覆盖“覆盖率诊断方法”。 +- 用例写法与证据结构见 `testcaseTemplate.md`。 +- 执行入口、模式隔离、覆盖目录口径见 `../../shared/ryanJsonCommon.md`。 + +## 分诊常用命令 +- unit 覆盖(全矩阵):`UNIT_MODE=full UNIT_SKIP_COV=0 bash scripts/ci/runBaseCoverage.sh` +- fuzz 覆盖(夜间预算):`FUZZ_MODE=nightly FUZZ_SKIP_COV=0 bash scripts/ci/runCoverage.sh` + +## 解读原则 +- 行覆盖高不代表质量高;优先看关键分支的 true/false 是否都触发。 +- 对 `RyanJsonCheckReturnFalse` 这类宏,重点看失败分支是否有证据。 +- 覆盖率不能替代语义断言与泄漏检查。 + +## 定位流程 +1. 锁定未覆盖行对应函数和前置条件。 +2. 判断是否被更早分支拦截(前置校验/类型判断/宏条件)。 +3. 先写确定性单测触达,再用 fuzz 扩展组合路径。 +4. 复跑覆盖率,验证 true/false 双向都触发。 + +## 典型问题定位 +- 分支只走 false:通常是输入构造不到位,或前置判断提前拦截。 +- 分支只走 true:可能是防御过强或路径被随机策略短路。 +- 覆盖下降但测试通过:检查是否引入不可达代码或常真/常假条件。 + +## 回归闭环 +1. 从失败日志还原最小输入。 +2. 先加确定性单测,再加 fuzzer 变体触发。 +3. 复跑覆盖率,确认目标分支双向触发。 +4. 固化 crash 样本到 corpus,防止回归。 + +## 定位技巧(当前脚本行为) +- unit 报告是多组 `profraw` 合并后的总览。 +- fuzz 会先在终端彩色打印 `llvm-cov report`,再写入文本报告。 +- 如果只做快速真假分支定位,可先 `UNIT_MODE=quick` 或 `FUZZ_MODE=quick` 缩短反馈周期,再切 full/nightly 做最终证据。 + +## 不应强求覆盖的路径 +- 仅在内存破坏下可达的 assert 路径。 +- 平台相关、编译选项关闭的不可达分支。 diff --git a/skills/ryanjson-test-engineering/references/fuzzerPlaybook.md b/skills/ryanjson-test-engineering/references/fuzzerPlaybook.md new file mode 100644 index 0000000..ec13b0e --- /dev/null +++ b/skills/ryanjson-test-engineering/references/fuzzerPlaybook.md @@ -0,0 +1,60 @@ +# Fuzzer 测试补充策略 + +## 范围 +- 本页只保留 Fuzzer 特有策略(输入扰动、种子、崩溃处理)。 +- 通用交付结构见 `testcaseTemplate.md`。 +- 单元测试差异规则见 `unityPlaybook.md`。 +- 执行入口、覆盖目录、脚本默认值口径见 `../../shared/ryanJsonCommon.md`。 + +## 前置初始化(必须) +- 进入 `LLVMFuzzerTestOneInput` 后,先确保 `RyanJsonInitHooks` 已初始化。 +- 所有随机创建/替换/打印路径都要走 hooks 分配器。 +- 需要故障注入时,通过 hooks 驱动,不直接改核心 API 逻辑。 +- fuzz 构建使用专用 target:`xmake -b RyanJsonFuzz`(该目标内置 `isEnableFuzzer` + `-fsanitize=fuzzer`)。 +- 覆盖链路统一使用 `scripts/ci/runCoverage.sh`(fuzzer 执行 + `llvm-profdata` + `llvm-cov`)。 +- 本地常规优先 `./run_local_fuzz.sh`;只有特殊测试(自定义参数)才直调 `scripts/ci/runCoverage.sh`。 + +## 脚本参数约定(`scripts/ci/runCoverage.sh`) +- 模式预算:`quick/nightly/full` 分别对应短/中/长预算(具体默认值以 `scripts/ci/runCoverage.sh` 为准)。 +- 次数优先级: + - 设置 `FUZZ_RUNS` 时按固定轮次执行(覆盖 `FUZZ_MAX_TOTAL_TIME`) + - 未设置 `FUZZ_RUNS` 时按 `FUZZ_MAX_TOTAL_TIME`(或模式默认值)执行 +- 关键透传参数: + - `FUZZ_TIMEOUT`、`FUZZ_MAX_LEN`、`FUZZ_VERBOSITY` + - `FUZZ_CORPUS_DIR`、`FUZZ_DICT_PATH` + - `FUZZ_EXTRA_ARGS` +- 构建策略: + - 默认增量:`XMAKE_FORCE_CLEAN=0` + - 强制清理:`XMAKE_FORCE_CLEAN=1`(仅在怀疑缓存污染时使用) + +## 目标 +- 让随机输入触达防御分支,不只跑通 happy path。 +- 保证 fuzzer 断言不与可配置语义冲突。 + +## 输入策略 +- 结构扰动:对象/数组嵌套、重复 key、空 key、混合类型。 +- 数值扰动:超长整数、小数尾长、指数边界、非法符号。 +- 字符串扰动:转义截断、非法 Unicode、代理对不完整。 +- 状态扰动:parse 成功后执行 create/replace/detach/delete 组合。 + +## 断言策略 +- 避免对“可能合法也可能非法”的输入写硬断言。 +- 对宏控制行为使用条件断言。 +- 崩溃优先级高于语义偏差;先修内存安全,再修预期一致性。 +- 所有权断言要区分:Add/Insert 失败(游离 item 由库侧清理)与 Replace 失败(item 不消费)。 + +## 覆盖策略 +- 为目标分支准备定向种子(不仅依赖随机变异)。 +- 将历史 crash 文件固化到 corpus。 +- 对新增防御分支记录触达证据(日志或覆盖率)。 + +## 与单元测试分工 +- 单元测试负责精确语义与回归基准。 +- fuzzer 负责发现未知组合路径与内存错误。 + +## 依据(仓库内) +- `test/fuzzer/entry.c`:`LLVMFuzzerTestOneInput` 中 hooks 初始化与断言 +- `xmake.lua`:`target("RyanJsonFuzz")` 的 fuzz 宏与 `-fsanitize=fuzzer` 配置 +- `scripts/ci/runCoverage.sh`:fuzz 覆盖执行链与参数默认值 +- `test/fuzzer/cases/fuzzerCreate.c`:Add/Insert 失败与游离态分支 +- `test/fuzzer/cases/fuzzerReplace.c`:Replace 失败不消费 `item` diff --git a/skills/ryanjson-test-engineering/references/geminiCompat.md b/skills/ryanjson-test-engineering/references/geminiCompat.md new file mode 100644 index 0000000..bfb205e --- /dev/null +++ b/skills/ryanjson-test-engineering/references/geminiCompat.md @@ -0,0 +1,26 @@ +# Gemini 兼容说明(测试类任务) + +## 范围 +- 本页只定义 Gemini 在测试任务的输入/输出结构。 +- 测试执行口径以 `../../shared/ryanJsonCommon.md` 为准。 + +## 推荐请求格式 +- 任务:扩展哪一类测试(core/equality/performance/utils/fuzzer)。 +- 约束:不新建文件、是否允许额外内存、当前宏配置。 +- 验收:期望覆盖的行/分支、泄漏标准、回归命令。 + +## 推荐输出格式 +1. 失败根因(1-2 行)。 +2. 测试改动清单(文件 + 函数 + 目标分支)。 +3. 覆盖到的分支与风险。 +4. 剩余未覆盖项与下一步。 + +字段组织可直接复用 `testcaseTemplate.md`。 + +## 对齐策略 +- 与 Codex 保持同一断言语义与文件归档规则。 +- 明确区分“单测断言失败”和“fuzzer 崩溃”。 +- 对宏开关语义必须显式说明。 + +## 术语字典(统一) +- 本文术语统一以 `terminology.md` 为准。 diff --git a/skills/ryanjson-test-engineering/references/regressionMatrix.md b/skills/ryanjson-test-engineering/references/regressionMatrix.md new file mode 100644 index 0000000..cce5549 --- /dev/null +++ b/skills/ryanjson-test-engineering/references/regressionMatrix.md @@ -0,0 +1,44 @@ +# 回归矩阵(测试工程) + +## 范围 +- 本页只定义回归维度与门禁。 +- 问题描述、改动清单、证据输出见 `testcaseTemplate.md`。 +- 执行口径与脚本参数基线见 `../../shared/ryanJsonCommon.md`。 + +## 语义回归 +- Parse 成功/失败语义。 +- Add/Insert/Replace/Detach/Delete 所有权语义。 +- Compare 有序/乱序语义。 +- Parse 默认非严格尾部语义(`RyanJsonParse`)与 strict 选项语义(`RyanJsonParseOptions`)。 +- Minify 终止符语义:`ret < textLen` 写 `\0`,`ret == textLen` 不写 `\0`。 +- Replace 失败语义:调用方负责复用或释放 `item`(库不消费)。 + +## 稳定性回归 +- ASan 崩溃是否消失。 +- 泄漏检测是否归零。 +- 历史 crash 样本是否稳定通过。 + +## 覆盖率回归 +- 目标分支 true/false 是否都有触达。 +- 新增防御分支是否有用例覆盖。 +- `RyanJsonInternalCalcLenBytes` 边界:255/256、65535/65536。 + +## 配置回归 +- 严格/非严格宏下断言是否一致。 +- hooks 初始化路径在各测试入口是否生效。 + +## 执行策略 +- 本地常规:优先 `run_local_*`。 +- 特殊回归:按需直调 `scripts/ci/runBaseCoverage.sh` / `scripts/ci/runCoverage.sh`。 + +## 推荐验收门禁 +1. 改动模块相关测试全部通过。 +2. 全量 unity 通过且无泄漏。 +3. fuzzer 冒烟通过并包含历史 crash 样本。 +4. 覆盖审阅时保留 unit/fuzz 报告路径(见 shared 约定)。 + +## 依据(仓库内) +- `RyanJson/RyanJsonParse.c`:`RyanJsonParse` 默认 `requireNullTerminator=RyanJsonFalse` +- `RyanJson/RyanJson.c`:`RyanJsonMinify` 终止符写入条件 +- `RyanJson/RyanJsonItem.c`:Insert/Replace 失败语义 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`、`test/unityTest/cases/utils/testUtils.c` diff --git a/skills/ryanjson-test-engineering/references/terminology.md b/skills/ryanjson-test-engineering/references/terminology.md new file mode 100644 index 0000000..4b9acf2 --- /dev/null +++ b/skills/ryanjson-test-engineering/references/terminology.md @@ -0,0 +1,4 @@ +# 术语字典 + +- 统一术语定义复用共享文档:`../../shared/terminology.md`。 +- 如出现本技能专属术语,可在本文件追加扩展,不覆盖共享定义。 diff --git a/skills/ryanjson-test-engineering/references/testcaseTemplate.md b/skills/ryanjson-test-engineering/references/testcaseTemplate.md new file mode 100644 index 0000000..d308747 --- /dev/null +++ b/skills/ryanjson-test-engineering/references/testcaseTemplate.md @@ -0,0 +1,34 @@ +# 测试任务模板(复用) + +## 范围 +- 适用于 RyanJson 单元测试与模糊测试的统一交付结构。 +- 本模板负责“目标分支/改动清单/证据/回归”骨架;框架差异见 unity/fuzzer playbook。 +- 执行入口与覆盖目录口径见 `../../shared/ryanJsonCommon.md`。 + +## 1) 问题与目标 +- 失败现象: +- 目标分支: +- 预期语义(可恢复错误/不可恢复错误): + +## 2) 测试改动清单 +- 文件: +- 用例函数: +- 场景意图: +- 所有权断言(Add/Insert vs Replace): + +## 3) 覆盖与稳定性证据 +- 执行入口(unit/fuzz + 脚本): +- 覆盖触达(true/false): +- 泄漏结果: +- 崩溃结果: +- 覆盖率报告路径: + +## 4) 回归与下一步 +- 已验证边界: +- 未验证项(推断): +- 下一批补测建议: + +## 依据(仓库内) +- `test/unityTest/cases/core/testCreate.c`:Insert/AddItem 失败断言模板 +- `test/unityTest/cases/core/testReplace.c`:Replace 失败不消费断言模板 +- `test/fuzzer/cases/fuzzerReplace.c`:fuzz 失败语义复核模板 diff --git a/skills/ryanjson-test-engineering/references/unityPlaybook.md b/skills/ryanjson-test-engineering/references/unityPlaybook.md new file mode 100644 index 0000000..27d9504 --- /dev/null +++ b/skills/ryanjson-test-engineering/references/unityPlaybook.md @@ -0,0 +1,47 @@ +# Unity 测试补充策略 + +## 范围 +- 本页只保留 Unity 特有编写规范。 +- 通用交付结构见 `testcaseTemplate.md`。 +- Fuzzer 差异规则见 `fuzzerPlaybook.md`。 +- 执行入口与覆盖目录基线见 `../../shared/ryanJsonCommon.md`。 + +## 前置初始化(必须) +- 在测试 runner 或每组 setup 中先调用 `RyanJsonInitHooks`。 +- hooks 初始化失败时应立即终止该组测试。 +- 内存统计、泄漏判定、OOM 注入都应通过 hooks 统一实现。 + +## 分类归档规则 +- 核心 API 行为:`test/unityTest/cases/core/` +- 相等性与往返:`test/unityTest/cases/equality/` +- 工具/鲁棒性:`test/unityTest/cases/utils/` +- 压力/资源:`test/unityTest/cases/performance/` + +## 编写顺序 +1. 成功路径:确认主语义。 +2. 失败路径:参数非法、插入失败、替换失败。 +3. 边界路径:空对象、空数组、极短/极长字符串、索引 0/中间/末尾。 +4. 资源路径:失败后是否自动释放,是否留下脏链。 + +## 典型断言模板 +- 返回值:`TEST_ASSERT_TRUE/FALSE`。 +- 指针语义:`TEST_ASSERT_NULL/NOT_NULL`。 +- 结构语义:`RyanJsonCompare` 或字段级验证。 +- 泄漏语义:结合测试内存统计钩子。 + +## 用例结构建议 +- 一个用例验证一个核心语义,避免“多目标混测”。 +- 测试名称包含场景 + 期望(例如 `testReplaceByKeyRejectDuplicateKeyStrict`)。 +- 失败路径应同时验证返回值与对象结构未损坏。 + +## 容易漏掉的点 +- “刚好够用”边界(长度 == 容量)。 +- 错误路径下 item 所有权转移(Add/Insert 与 Replace 失败语义不同)。 +- 宏开关导致的双语义(严格/非严格)。 +- Detach 后节点再用时的游离状态。 + +## 依据(仓库内) +- `test/unityTest/runner/main.c`、`test/unityTest/common/testCommon.c`:runner/setup hooks 初始化 +- `test/unityTest/cases/core/testCreate.c`:Insert/AddItem 失败与游离态断言 +- `test/unityTest/cases/core/testReplace.c`:Replace 失败不消费语义 +- `test/unityTest/cases/utils/testPrint.c`:preallocated 刚好够用/不足边界 diff --git a/skills/ryanjson-test-engineering/sop.md b/skills/ryanjson-test-engineering/sop.md new file mode 100644 index 0000000..5e06a98 --- /dev/null +++ b/skills/ryanjson-test-engineering/sop.md @@ -0,0 +1,31 @@ +# RyanJson 测试工作流(压缩版) + +## 1. 入口与模式 +1. 先确认模式(unit/fuzz)和宏前提。 +2. 本地常规回归优先 `run_local_*`。 +3. 特殊测试(矩阵/并发/轮次/覆盖)再直调 `scripts/ci/*`。 + +## 2. 补测循环 +1. 锁定未覆盖分支或崩溃信号。 +2. 找到阻断分支可达性的 guard。 +3. 先加最小确定性 unit 用例。 +4. 需要时补 fuzz 样本和触达路径。 +5. 复跑并确认双向分支触达。 + +## 3. 泄漏/崩溃分诊 +1. 按所有权、边界、结构不变量分类。 +2. 关联到 API 家族(Create/Add/Replace/Detach/Change)。 +3. 在失败路径补释放断言。 + +## 4. 交付格式 +1. 根因摘要。 +2. 修改的测试文件与函数。 +3. 证据(模式、分支、泄漏/崩溃状态、覆盖率报告路径)。 +4. 未覆盖边界与下一步补测建议。 + +## 5. 依据(仓库内) +- `xmake.lua`:`RyanJson` / `RyanJsonFuzz` 模式 target +- `test/unityTest/runner/main.c`:unit 模式入口隔离 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c`:失败路径断言样例 +- `scripts/ci/runBaseCoverage.sh`:unit 矩阵与合并覆盖率(`coverage/unitMatrix`) +- `scripts/ci/runCoverage.sh`:fuzz 覆盖率复核链路(`coverage/fuzz`) diff --git a/skills/ryanjson-test-engineering/testArchitecture.md b/skills/ryanjson-test-engineering/testArchitecture.md new file mode 100644 index 0000000..e870b9d --- /dev/null +++ b/skills/ryanjson-test-engineering/testArchitecture.md @@ -0,0 +1,43 @@ +# RyanJson 测试架构(压缩版) + +## 1. 分层 +### 1.1 Unity 层(`test/unityTest/`) +- 验证可确定契约行为。 +- 使用显式断言和分支目标用例。 + +### 1.2 Fuzzer 层(`test/fuzzer/`) +- 验证随机/异常输入下的稳健性。 +- 核心目标是无崩溃/无泄漏/无 UB,而非固定值断言。 + +## 2. 放置规则 +- 优先扩展现有分类文件。 +- 非必要不新增零散测试文件。 +- 分类保持稳定:core/equality/utils/performance/fuzzer。 + +## 2.1 执行层(脚本职责) +- 脚本入口、模式参数、覆盖率目录口径统一见 `../shared/ryanJsonCommon.md`。 +- 本页仅强调职责差异:unit 负责确定性语义,fuzz 负责随机路径稳健性。 + +## 3. 新增测试流程 +1. 选定目标分类文件。 +2. 添加最小可复现用例。 +3. 必要时注册 runner 入口。 +4. 在正确模式下验证。 +5. 复核覆盖率与所有权断言。 + +## 4. Fuzzer 流程 +1. 增加可复现崩溃种子/corpus。 +2. 扩展对应 fuzz case 路径。 +3. 复跑确认历史崩溃不再复现。 +4. 若有分支目标,再同步覆盖率复核。 + +## 5. 模式隔离规则 +- unit 与 fuzz 的入口和假设不同。 +- 禁止把两种模式写成同一条混合执行建议。 +- 推荐构建命令:unit 用 `xmake -b RyanJson`,fuzz 用 `xmake -b RyanJsonFuzz`。 + +## 6. 依据(仓库内) +- `test/unityTest/runner/main.c`:`#ifndef isEnableFuzzer` 的 unit 入口 +- `test/fuzzer/entry.c`:`LLVMFuzzerTestOneInput` 入口 +- `xmake.lua`:`RyanJson` / `RyanJsonFuzz` 目标配置 +- `../shared/ryanJsonCommon.md`:统一执行入口与覆盖口径 diff --git a/skills/shared/ryanJsonCommon.md b/skills/shared/ryanJsonCommon.md new file mode 100644 index 0000000..6285c0d --- /dev/null +++ b/skills/shared/ryanJsonCommon.md @@ -0,0 +1,157 @@ +# RyanJson 共性规则 + +## 1. 范围 +- 本文件是以下三个技能的共性约束基线: + - `skills/ryanjson-api-usage` + - `skills/ryanjson-optimization` + - `skills/ryanjson-test-engineering` +- 各技能的领域细节仍以各自 `references/` 文档为准。 +- 统一术语字典:`terminology.md`。 + +## 2. 仓库事实(必须遵守) +- 主机侧主入口是 `xmake`。 +- 当前仓库通过 target 区分模式: + - `RyanJson`:默认 unit 目标(不注入 libFuzzer main) + - `RyanJsonFuzz`:专用 fuzz 目标(启 `isEnableFuzzer` + `-fsanitize=fuzzer`) + - `RyanJsonQemu` / `RyanJsonQemuCm3`:QEMU 目标(FreeRTOS Cortex-M,含非对齐访问 fault 校验) +- QEMU 非对齐基线(默认约束): + - 运行时开启 `SCB->CCR.UNALIGN_TRP=1`,以硬件语义捕获非对齐访问。 + - 不在 QEMU 目标上启用 `-mno-unaligned-access`,避免编译器辅助对齐掩盖真实语义。 + - `YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS=1` 作为第三方库局部防御开关保留,除非用户明确要求调整。 +- 覆盖脚本分工: + - `scripts/ci/runBaseCoverage.sh`:单元测试矩阵(`quick=2` / `nightly=4` / `full=8`) + - `scripts/ci/runCoverage.sh`:fuzzer 执行与覆盖率生成 +- 本地便捷入口在仓库根目录: + - `run_local_base.sh`:本地一键 unit 矩阵 + - `run_local_qemu.sh`:本地一键 QEMU 矩阵(默认 full,覆盖 localbase 用例并校验对齐异常) + - `run_local_ci.sh`:本地模拟 `ci-pr`(unit + quick fuzz) + - `run_local_fuzz.sh`:本地固定 6 并发 fuzz + - 默认值摘要: + - `run_local_base.sh`:`UNIT_MODE=full`、`UNIT_SKIP_COV=1` + - `run_local_qemu.sh`:`QEMU_MODE=full`、`QEMU_STOP_ON_FAIL=1` + - `run_local_ci.sh`:full unit + quick fuzz(`FUZZ_SKIP_COV=1`) + - `run_local_fuzz.sh`:`FUZZ_RUNS=10000`、`FUZZ_WORKERS/JOBS=6` + - `run_local_qemu.sh` 默认保留 ANSI 颜色输出;仅在用户明确要求“去色/净化日志”时再剥离控制符。 +- 覆盖率目录固定且每次执行前清理(仅保留最新结果): + - unit:`coverage/unitMatrix`(`report.txt` + `html/`) + - fuzz:`coverage/fuzz`(`report.txt` + `html/`) +- `Makefile` 为历史辅助,不是当前主流程。 +- `SConscript` 主要用于 RT-Thread 软件包集成。 + +### 2.1 依据(仓库内) +- `xmake.lua`:`target("RyanJson")` 与 `target("RyanJsonFuzz")` 的模式分离 +- `xmake.lua`:`RyanJsonFuzz` 中 `add_defines("isEnableFuzzer")` 与 `-fsanitize=fuzzer` +- `xmake.lua`:`RyanJson` 为链接兼容补入 `test/fuzzer/utils/fuzzerDriver.c`、`fuzzerMemory.c`(不启用 fuzz sanitizer) +- `test/unityTest/runner/main.c`:`#ifndef isEnableFuzzer` 包裹 Unity `main` +- `scripts/ci/runBaseCoverage.sh`:unit 矩阵执行与合并覆盖率 +- `scripts/ci/runCoverage.sh`:构建/执行 `RyanJsonFuzz` + `llvm-cov` +- `run_local_base.sh`、`run_local_qemu.sh`、`run_local_ci.sh`、`run_local_fuzz.sh`:本地入口封装 +- `test/qemu/platform/qemuStartup.c`:`SCB->CCR.UNALIGN_TRP=1` 的运行时设置与启动日志 +- `xmake.lua`:QEMU target 保留 `YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS=1`,不启用 `-mno-unaligned-access` +- `run_local_qemu.sh`:QEMU 流清洗默认保留 ANSI `ESC`(颜色输出) + +## 3. 语义取证链(不确定时必须执行) +1. `example/` +2. `test/unityTest/` +3. `test/fuzzer/` +- 取证不完整时,结论必须标注为“推断”。 + +## 3.1 证据优先(回答必须可追溯) +- 涉及语义细节时(如 `Print/Minify`、`Add/Insert/Replace` 失败所有权、宏分支行为),优先依据当前源码与单元测试。 +- 默认证据顺序: + 1. 头文件与实现(`RyanJson/*.h`、`RyanJson/*.c`) + 2. 单元测试(`test/unityTest/`) + 3. 模糊测试(`test/fuzzer/`) +- 输出建议中应给出至少一个可追溯依据路径;没有依据时必须明确写“推断”。 + +## 4. 宏基线检查(回答前必须做) +- 必查当前源码中的: + - `RyanJsonStrictObjectKeyCheck` + - `RyanJsonDefaultAddAtHead` +- 当前仓库默认值(仅作初始参考,最终以当前源码为准): + - `RyanJsonStrictObjectKeyCheck=false` + - `RyanJsonDefaultAddAtHead=false` + +## 5. 所有权共识(统一口径) +- `Create*` 成功:节点归调用方,直到挂载或显式释放。 +- `Detach*` 成功:返回节点归调用方。 +- `Add/Insert` 与 `Replace` 的失败语义不得混用。 +- 当前实现中: + - `Add/Insert` 失败时,游离 `item` 走库侧清理;非游离 `item` 返回失败但不释放(保护原树)。 + - `Replace` 失败不消费新节点,调用方需复用或释放。 +- 对边界语义有疑问时,必须回查当前头文件与测试。 + +### 5.1 依据(仓库内) +- `RyanJson/RyanJsonItem.c`:`RyanJsonInsert` 失败语义(游离 `item` 走 `error__` 删除,非游离 `item` 早返回不删除) +- `RyanJson/RyanJsonItem.c`:`RyanJsonReplaceByKey/ByIndex` 失败路径返回 false,不删除新 `item` +- `test/unityTest/cases/core/testCreate.c`:已挂树节点被拒绝插入 +- `test/unityTest/cases/core/testReplace.c`:Replace 失败后 `item` 仍可复用并由调用方释放 + +## 6. 测试模式契约 +- unit:走 Unity `main` 入口,不允许 fuzz sanitizer 注入 `main`。 +- qemu:走 `RyanJsonQemu*` 目标,校验 FreeRTOS 调度与非对齐访问 fault 语义。 +- fuzz:按 fuzz 宏/编译参数构建,使用 `scripts/ci/runCoverage.sh` 进行覆盖链路。 +- 禁止在同一条执行建议中混写 unit/qemu/fuzz 的前提。 +- 推荐命令: + - unit:`xmake -b RyanJson` + - qemu:`xmake -b RyanJsonQemu` + - fuzz:`xmake -b RyanJsonFuzz` + +### 6.1 脚本调用建议(优先) +- 本地优先:直接运行根目录入口 + - `./run_local_base.sh` + - `./run_local_qemu.sh` + - `./run_local_ci.sh` + - `./run_local_fuzz.sh` +- CI/细粒度调参时再直调 `scripts/ci/*` + - unit 快检:`UNIT_MODE=quick UNIT_SKIP_COV=1 bash scripts/ci/runBaseCoverage.sh` + - unit 全矩阵:`UNIT_MODE=full bash scripts/ci/runBaseCoverage.sh` + - fuzz 快检:`FUZZ_MODE=quick FUZZ_SKIP_COV=1 bash scripts/ci/runCoverage.sh` + - fuzz 覆盖:`FUZZ_MODE=nightly FUZZ_SKIP_COV=0 bash scripts/ci/runCoverage.sh` +- `XMAKE_FORCE_CLEAN=1` 仅在怀疑配置缓存污染时启用;默认增量模式更快。 +- `scripts/ci/runCoverage.sh` 会先把 `llvm-cov report --use-color` 输出到终端,再写入 `coverage/fuzz/report.txt`。 +- RFC8259 用例列表通过目录扫描(`readdir`)获取,不再维护 `rfc8259_filelist.inc`。 + +## 7. 输出契约 +- 输出必须显式区分: + - 已验证事实 + - 推断结论 + - 可恢复错误/不可恢复错误 +- 代码建议必须包含: + - 前置条件 + - 成功路径 + - 失败路径 + - 所有权与释放责任 + +## 8. 代码与审查基线 +- 类型统一 `stdint`,命名统一小驼峰。 +- 示例中避免魔法数字,优先使用命名常量。 +- 三目运算符(`?:`)可用于“无副作用、单行、明显更清晰”的场景;复杂分支统一使用 `if/else`。 +- 禁止把未验证内部行为当作确定事实输出。 +- API 使用类问题默认限制在公开接口,除非用户明确要求内部实现。 + +## 9. 注释规范(Doxygen) +- 配置宏、公开接口、关键内部函数的新增/改动注释,统一使用 Doxygen 风格。 +- 首行必须使用 `@brief` 描述“这是什么 + 做什么”。 +- 存在边界或默认行为时,补 `@note`;涉及原理或公式时,补 `@details`。 +- 禁止使用序号式注释前缀:如 `1.`、`2)`、`3.1`、`A.`、`Stage 1:`;改用语义化短句标题。 +- 参数/返回值注释按需使用: + - 函数参数:`@param` + - 返回值:`@return` +- 允许短注释使用 `//`;关键配置、公开接口、复杂逻辑优先使用 Doxygen 块注释。 +- Doxygen 注释至少三行,不使用单行 `/** @brief ... */` 形式。 +- 注释语言可中文优先,但“类型名 / 字段语义名 / API 名”保持英文原样,不做中文化。 +- 强制保留英文示例: + - 类型名:`Array`、`Object`、`Null`、`Bool`、`Int`、`Double` + - 字段语义名:`strValue`、`intValue`、`doubleValue`、`boolValue`、`objValue` + - API/宏名:`Add/Insert/Replace/Detach/Delete`、`RyanJsonAddPosition`、`RyanJsonInlineStringSize` +- 若需中文解释,采用“英文术语 + 中文说明”形式,例如:`strValue(字符串载荷)`。 +- 推荐模板: + +```c +/** + * @brief 说明对象与作用。 + * @note 说明默认行为或限制条件。 + * @details 说明推导过程、公式或实现约束(可选)。 + */ +``` diff --git a/skills/shared/terminology.md b/skills/shared/terminology.md new file mode 100644 index 0000000..0e041ef --- /dev/null +++ b/skills/shared/terminology.md @@ -0,0 +1,15 @@ +# RyanJson 术语字典(共享) + +- **已验证**:有直接证据支撑(代码、测试、日志、覆盖率、回归结果)。 +- **推断**:暂无直接证据,仅基于已有信息推理,输出时必须显式标注。 +- **未验证**:尚未执行验证;通常等同“推断”或“待验证项”。 +- **可恢复错误**:应返回 `false/NULL` 且完成回滚/释放,不应用 assert 终止流程。 +- **不可恢复错误**:内部不变量或结构被破坏;启用 `RyanJsonEnableAssert` 时可触发 assert。 +- **失败语义**:失败时的所有权与资源处理规则;`Add/Insert` 与 `Replace` 必须分开描述。 +- **语义取证链**:`example/ -> test/unityTest/ -> test/fuzzer/`。 +- **术语保留英文**:注释可用中文,但类型名/字段语义名/API 名保持英文(如 `Array`、`Object`、`strValue`、`objValue`)。 + +## 依据(仓库内) +- `RyanJson/RyanJson.h`:公开 API 与失败语义注释 +- `RyanJson/RyanJsonItem.c`:Add/Insert/Replace 失败路径 +- `test/unityTest/cases/core/testCreate.c`、`test/unityTest/cases/core/testReplace.c` diff --git a/test/RyanJsonMemoryFootprintTest.c b/test/RyanJsonMemoryFootprintTest.c deleted file mode 100644 index 55b3982..0000000 --- a/test/RyanJsonMemoryFootprintTest.c +++ /dev/null @@ -1,247 +0,0 @@ -#include "RyanJsonTest.h" - -static void *yy_malloc(void *ctx, size_t size) { return v_malloc(size); } -static void *yy_realloc(void *ctx, void *ptr, size_t old_size, size_t size) { return v_realloc(ptr, size); } -static void yy_free(void *ctx, void *ptr) { v_free(ptr); } - -static int RyanJsonMemoryFootprint(char *jsonstr) -{ - int32_t use = vallocGetUse(); - RyanJsonInitHooks(v_malloc, v_free, v_realloc); - - RyanJson_t json = RyanJsonParse(jsonstr); - if (json == NULL) - { - printf("%s:%d 解析失败\r\n", __FILE__, __LINE__); - return -1; - } - - use = vallocGetUse() - use; - - RyanJsonDelete(json); - return use; -} - -static int cJSONMemoryFootprint(char *jsonstr) -{ - int32_t use = vallocGetUse(); - cJSON_Hooks hooks = {.malloc_fn = v_malloc, .free_fn = v_free}; - cJSON_InitHooks(&hooks); - - cJSON *json = cJSON_Parse(jsonstr); - if (json == NULL) - { - printf("%s:%d 解析失败\r\n", __FILE__, __LINE__); - return -1; - } - - use = vallocGetUse() - use; - cJSON_Delete(json); - return use; -} - -static int yyjsonMemoryFootprint(char *jsonstr) -{ - static yyjson_alc yyalc = {yy_malloc, yy_realloc, yy_free, NULL}; - // 先解析成只读文档(可用自定义分配器 yyalc) - yyjson_doc *doc = yyjson_read_opts(jsonstr, strlen(jsonstr), YYJSON_READ_NOFLAG, &yyalc, NULL); - if (doc == NULL) { return -1; } - - // 从只读文档拷贝为可变文档(用于后续读写修改) - yyjson_mut_doc *mdoc = yyjson_doc_mut_copy(doc, &yyalc); - yyjson_doc_free(doc); - if (mdoc == NULL) { return -1; } - - // 统计当前分配器的占用 - int area = 0, use = 0; - v_mcheck(&area, &use); - - // 用完释放可变文档 - yyjson_mut_doc_free(mdoc); - return use; -} - -static void printfJsonCompera(char *jsonstr) -{ - int RyanJsonCount = 0; - int cJSONCount = 0; - int yyjsonCount = 0; - RyanJsonCount = RyanJsonMemoryFootprint(jsonstr); - cJSONCount = cJSONMemoryFootprint(jsonstr); - yyjsonCount = yyjsonMemoryFootprint(jsonstr); - printf("json原始文本长度为 %ld, 序列化后RyanJson内存占用: %d, cJSON内存占用: %d, yyjson内存占用: %d\r\n", strlen(jsonstr), - RyanJsonCount, cJSONCount, yyjsonCount); - - double save_vs_cjson = 100.0 - ((double)RyanJsonCount * 100.0) / (double)cJSONCount; - double save_vs_yyjson = 100.0 - ((double)RyanJsonCount * 100.0) / (double)yyjsonCount; - - printf("比cJSON节省: %.2f%% 内存占用, 比yyjson节省: %.2f%% 内存占用\r\n", save_vs_cjson, save_vs_yyjson); -} - -RyanJsonBool_e RyanJsonMemoryFootprintTest(void) -{ - char *jsonstr; - - printf("\r\n--------------------------- 混合类型json数据测试 --------------------------\r\n"); - jsonstr = "{\"item1\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89," - "16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\",\"hello\"," - "\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16,\"double\":16.89," - "\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false," - "\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]" - "},\"item2\":{" - "\"inter\":16,\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":{\"inter\":16,\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":" - "true,\"boolFalse\":false,\"null\":null}," - "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\"," - "\"hello\"],\"array\":[16,16.89,\"hello\"," - "true,false,null],\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":" - "false,\"null\":null},{" - "\"inter\":16,\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]},\"item3\":{\"inter\":16,\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":" - "true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "\"arrayInt\":[16,16,16,16," - "16],\"arrayDouble\":[16.89,16.89,16.89," - "16.89,16.89],\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true," - "false,null]," - "\"arrayItem\":[{\"inter\":16,\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":" - "true,\"boolFalse\":false,\"null\":null}]}" - ",\"item4\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89," - "16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\",\"hello\"," - "\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16,\"double\":16.89," - "\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false," - "\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]" - "}}"; - printfJsonCompera(jsonstr); - - printf("\r\n--------------------------- 对象占多json数据测试 --------------------------\r\n"); - jsonstr = - "{\"message\":\"success感谢又拍云(upyun.com)提供CDN赞助\",\"status\":200,\"date\":\"20230822\",\"time\":\"2023-08-22 " - "09:44:54\",\"cityInfo\":{\"city\":\"郑州市\",\"citykey\":\"101180101\",\"parent\":\"河南\",\"updateTime\":\"07:46\"}," - "\"data\":{\"shidu\":" - "\"85%\",\"pm25\":20,\"pm10\":56," - "\"quality\":\"良\",\"wendu\":\"29\",\"ganmao\":\"极少数敏感人群应减少户外活动\",\"forecast\":[{\"date\":\"22\",\"high\":" - "\"高温 " - "35℃\",\"low\":\"低温 " - "23℃\",\"ymd\":\"2023-08-22\",\"week\":\"星期二\",\"sunrise\":\"05:51\",\"sunset\":\"19:05\",\"aqi\":78,\"fx\":\"东南风\"," - "\"fl\":\"2级\"," - "\"type\":\"晴\",\"notice\":" - "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"23\",\"high\":\"高温 33℃\",\"low\":\"低温 " - "23℃\",\"ymd\":\"2023-08-23\",\"week\":\"星期三\",\"sunrise\":\"05:52\",\"sunset\":\"19:04\",\"aqi\":71,\"fx\":\"南风\"," - "\"fl\":\"2级\"," - "\"type\":\"中雨\",\"notice\":" - "\"记得随身携带雨伞哦\"},{\"date\":\"24\",\"high\":\"高温 31℃\",\"low\":\"低温 " - "21℃\",\"ymd\":\"2023-08-24\",\"week\":\"星期四\",\"sunrise\":\"05:52\",\"sunset\":\"19:03\",\"aqi\":74,\"fx\":\"东风\"," - "\"fl\":\"2级\"," - "\"type\":\"晴\",\"notice\":" - "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"25\",\"high\":\"高温 30℃\",\"low\":\"低温 " - "23℃\",\"ymd\":\"2023-08-25\",\"week\":\"星期五\",\"sunrise\":\"05:53\",\"sunset\":\"19:02\",\"aqi\":93,\"fx\":\"东风\"," - "\"fl\":\"1级\"," - "\"type\":\"小雨\",\"notice\":" - "\"雨虽小,注意保暖别感冒\"},{\"date\":\"26\",\"high\":\"高温 25℃\",\"low\":\"低温 " - "22℃\",\"ymd\":\"2023-08-26\",\"week\":\"星期六\",\"sunrise\":\"05:54\",\"sunset\":\"19:00\",\"aqi\":80,\"fx\":\"东北风\"," - "\"fl\":\"1级\"," - "\"type\":\"阴\",\"notice\":" - "\"不要被阴云遮挡住好心情\"},{\"date\":\"27\",\"high\":\"高温 27℃\",\"low\":\"低温 " - "20℃\",\"ymd\":\"2023-08-27\",\"week\":\"星期日\",\"sunrise\":\"05:55\",\"sunset\":\"18:59\",\"aqi\":74,\"fx\":\"西北风\"," - "\"fl\":\"1级\"," - "\"type\":\"阴\",\"notice\":" - "\"不要被阴云遮挡住好心情\"},{\"date\":\"28\",\"high\":\"高温 30℃\",\"low\":\"低温 " - "20℃\",\"ymd\":\"2023-08-28\",\"week\":\"星期 " - "一\",\"sunrise\":\"05:55\",\"sunset\":\"18:58\",\"aqi\":80,\"fx\":\"东北风\",\"fl\":\"2级\",\"type\":\"多云\",\"notice\":" - "\"阴晴之间,谨防紫外线侵扰\"},{\"date\":\"29\",\"high\":" - "\"高温 30℃\",\"low\":\"低温 " - "20℃\",\"ymd\":\"2023-08-29\",\"week\":\"星期二\",\"sunrise\":\"05:56\",\"sunset\":\"18:56\",\"aqi\":80,\"fx\":\"东北风\"," - "\"fl\":\"2级\"," - "\"type\":\"多云\",\"notice\":" - "\"阴晴之间,谨防紫外线侵扰\"},{\"date\":\"30\",\"high\":\"高温 31℃\",\"low\":\"低温 " - "20℃\",\"ymd\":\"2023-08-30\",\"week\":\"星期三\",\"sunrise\":\"05:57\",\"sunset\":\"18:55\",\"aqi\":92,\"fx\":\"南风\"," - "\"fl\":\"1级\"," - "\"type\":\"晴\",\"notice\":" - "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"31\",\"high\":\"高温 33℃\",\"low\":\" 低温 " - "22℃\",\"ymd\":\"2023-08-31\",\"week\":\"星期四\",\"sunrise\":\"05:57\",\"sunset\":\"18:54\",\"aqi\":91,\"fx\":\"南风\"," - "\"fl\":\"1级\"," - "\"type\":\"晴\",\"notice\":" - "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"01\",\"high\":\"高温 34℃\",\"low\":\"低温 " - "23℃\",\"ymd\":\"2023-09-01\",\"week\":\"星期五\",\"sunrise\":\"05:58\",\"sunset\":\"18:52\",\"aqi\":91,\"fx\":\"西风\"," - "\"fl\":\"1级\"," - "\"type\":\"晴\",\"notice\":" - "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"02\",\"high\":\"高温 36℃\",\"low\":\"低温 " - "25℃\",\"ymd\":\"2023-09-02\",\"week\":\"星期六\",\"sunrise\":\"05:59\",\"sunset\":\"18:51\",\"aqi\":78,\"fx\":\"南风\"," - "\"fl\":\"1级\"," - "\"type\":\"阴\",\"notice\":" - "\"不要被阴云遮挡住好心情\"},{\"date\":\"03\",\"high\":\"高温 35℃\",\"low\":\"低温 " - "24℃\",\"ymd\":\"2023-09-03\",\"week\":\"星期日\",\"sunrise\":\"06:00\",\"sunset\":\"18:50\",\"aqi\":82,\"fx\":\"东北风\"," - "\"fl\":\"1级\"," - "\"type\":\"晴\",\"notice\":" - "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"04\",\"high\":\"高温 35℃\",\"low\":\"低温 " - "25℃\",\"ymd\":\"2023-09-04\",\"week\":\"星期一\",\"sunrise\":\"06:00\",\"sunset\":\"18:48\",\"aqi\":88,\"fx\":\"南风\"," - "\"fl\":\"2级\"," - "\"type\":\"晴\",\"notice\":" - "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"05\",\"high\":\"高温 35℃\",\"low\":\"低温 " - "25℃\",\"ymd\":\"2023-09-05\",\"week\":\"星期二\",\"sunrise\":\"06:01\",\"sunset\":\"18:47\",\"aqi\":58,\"fx\":\"南风\"," - "\"fl\":\"2级\"," - "\"type\":\"阴\",\"notice\":" - "\"不要被阴云遮挡住好心情\"}],\"yesterday\":{\"date\":\"21\",\"high\":\"高温 34℃\",\"low\":\"低温 " - "24℃\",\"ymd\":\"2023-08-21\",\"week\":\" " - "星期一\",\"sunrise\":\"05:50\",\"sunset\":\"19:07\",\"aqi\":60,\"fx\":\"西风\",\"fl\":\"2级\",\"type\":\"小雨\"," - "\"notice\":" - "\"雨虽小,注意保暖别感冒\"}}}"; - printfJsonCompera(jsonstr); - - printf("\r\n--------------------------- 数组占多json数据测试 --------------------------\r\n"); - jsonstr = "{\"item1\":{\"arrayInt\":[16,16,16,16,16,16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89,16.89,16.89,16." - "89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\",\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true," - "false,null,16,16.89," - "\"hello\",true,false,null]},\"item2\":{" - "\"arrayInt\":[16,16,16,16,16,16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16." - "89],\"arrayString\":[" - "\"hello\",\"hello\",\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null,16,16.89," - "\"hello\",true,false," - "null]},\"item3\":{\"arrayInt\":[16,16,16," - "16,16,16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89],\"arrayString\":[" - "\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null,16,16.89,\"hello\",true,false," - "null]},\"item4\":{" - "\"arrayInt\":[16,16,16,16,16,16,16,16,16,16]," - "\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\",\"hello\"," - "\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null,16,16.89,\"hello\",true,false,null]}}"; - printfJsonCompera(jsonstr); - - printf("\r\n--------------------------- 小对象json 混合类型内存占用测试 --------------------------\r\n"); - jsonstr = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}"; - printfJsonCompera(jsonstr); - - printf("\r\n--------------------------- 小对象json 纯字符串内存占用测试 --------------------------\r\n"); - jsonstr = "{\"inter\":\"16\",\"double\":\"16.89\",\"string\":\"hello\",\"boolTrue\":\"true\",\"boolFalse\":\"false\",\"null\":" - "\"null\"}"; - printfJsonCompera(jsonstr); - - /** - * @brief 反序列化为文本,内存占用没什么特别的优化点,和cjson实现思路差不多,内存占用也就差不多,就不进行对比了 - * - */ - - return RyanJsonTrue; -} diff --git a/test/RyanJsonRFC8259JsonTest.c b/test/RyanJsonRFC8259JsonTest.c deleted file mode 100644 index e2fa14d..0000000 --- a/test/RyanJsonRFC8259JsonTest.c +++ /dev/null @@ -1,606 +0,0 @@ -#include "RyanJsonTest.h" - -#define PrintfStrCmpEnable - -typedef int (*jsonParseData)(char *fileName, char *data, uint32_t len); - -/* Read a file, parse, render back, etc. */ -static int testFile(const char *path, jsonParseData jsonParseDataHandle) -{ - - DIR *dir = NULL; - struct dirent *entry; - - int path_len = strlen(path); - int count = 0; - int used_count = 0; - if (!path || !path_len || !(dir = opendir(path))) { goto fail; } - - while ((entry = readdir(dir))) - { - - char *name = (char *)entry->d_name; - - if (!name || !strlen(name)) { continue; } - - if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { continue; } - - char aaa[300] = {0}; - snprintf(aaa, sizeof(aaa), "%s/%s", path, name); - - FILE *f = fopen(aaa, "rb"); - if (f == NULL) { goto fail; } - - fseek(f, 0, SEEK_END); - long len = ftell(f); - fseek(f, 0, SEEK_SET); - char *data = (char *)malloc(len + 10); - fread(data, 1, len, f); - data[len] = '\0'; - fclose(f); - int status = 0; - - int startUse = vallocGetUse(); - status = jsonParseDataHandle(name, data, len); - - used_count++; - if (0 == strncmp("y_", name, 2)) - { - if (0 == status) { count++; } - else - { - printf("应该成功,但是失败: %s, len: %ld\n", data, len); - } - } - else if (0 == strncmp("n_", name, 2)) - { - if (0 != status) { count++; } - else - { - printf("应该失败,但是成功: %s, len: %ld\n", data, len); - } - } - else if (0 == strncmp("i_", name, 2)) { count++; } - - if (startUse != vallocGetUse()) - { - int area = 0, use = 0; - v_mcheck(&area, &use); - printf("内存泄漏 %s len: %ld\r\n", data, len); - free(data); - // printf("内存泄漏 %x len: %ld\r\n", (unsigned int)data, len); - // printf("内存泄漏 %c len: %ld\r\n", (int)data, len); - printf("|||----------->>> area = %d, size = %d\r\n", area, use); - break; - } - - // if (use != (len + 10)) - // { - // int area = 0, use = 0; - // v_mcheck(&area, &use); - // free(data); - // printf("内存泄漏 %s len: %ld\r\n", data, len); - // // printf("内存泄漏 %x len: %ld\r\n", (unsigned int)data, len); - // // printf("内存泄漏 %c len: %ld\r\n", (int)data, len); - // printf("|||----------->>> area = %d, size = %d\r\n", area, use); - // break; - // } - free(data); - } - - closedir(dir); - - printf("RFC 8259 JSON: (%d/%d)\r\n", count, used_count); - return 0; - -fail: - if (dir) { closedir(dir); } - - return -1; -} - -typedef struct -{ - const char *p; - int32_t len; -} Slice; - -static int hexval(int c) -{ - if (c >= '0' && c <= '9') { return c - '0'; } - c = (c >= 'a' && c <= 'f') ? (c - 'a' + 'A') : c; - if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } - return -1; -} - -static int decode_u4(const char *s, uint16_t *out) -{ - int h0 = hexval(s[0]), h1 = hexval(s[1]), h2 = hexval(s[2]), h3 = hexval(s[3]); - if (h0 < 0 || h1 < 0 || h2 < 0 || h3 < 0) { return 0; } - *out = (uint16_t)((h0 << 12) | (h1 << 8) | (h2 << 4) | h3); - return 1; -} - -// 将 JSON 字符串(不含两端引号)规范化为 UTF-8 字节序列 -static int normalize_json_string(const char *in, int32_t in_len, unsigned char **out, int32_t *out_len) -{ - // 预留足够缓冲 - int32_t cap = in_len * 4 + 8; - unsigned char *buf = (unsigned char *)malloc(cap); - if (!buf) { return 0; } - int32_t pos = 0; - - for (int32_t i = 0; i < in_len; i++) - { - unsigned char ch = (unsigned char)in[i]; - - if (ch == '\\') - { - if (i + 1 >= in_len) - { - free(buf); - return 0; - } - unsigned char esc = (unsigned char)in[++i]; - switch (esc) - { - case '\"': buf[pos++] = '\"'; break; - case '\\': buf[pos++] = '\\'; break; - case '/': buf[pos++] = '/'; break; - case 'b': buf[pos++] = '\b'; break; - case 'f': buf[pos++] = '\f'; break; - case 'n': buf[pos++] = '\n'; break; - case 'r': buf[pos++] = '\r'; break; - case 't': buf[pos++] = '\t'; break; - case 'u': { - if (i + 4 >= in_len) - { - free(buf); - return 0; - } - uint16_t u1; - if (!decode_u4(&in[i + 1], &u1)) - { - free(buf); - return 0; - } - i += 4; - - if (u1 >= 0xD800 && u1 <= 0xDBFF) - { - // 高代理,必须跟 \uXXXX 低代理 - if (i + 2 >= in_len || in[i + 1] != '\\' || in[i + 2] != 'u' || i + 6 >= in_len) - { - free(buf); - return 0; - } - i += 2; - uint16_t u2; - if (!decode_u4(&in[i + 1], &u2)) - { - free(buf); - return 0; - } - i += 4; - if (!(u2 >= 0xDC00 && u2 <= 0xDFFF)) - { - free(buf); - return 0; - } - // 组合码点 - uint32_t cp = 0x10000 + (((uint32_t)(u1 - 0xD800) << 10) | (uint32_t)(u2 - 0xDC00)); - // 编码为 UTF-8 - buf[pos++] = (unsigned char)(0xF0 | ((cp >> 18) & 0x07)); - buf[pos++] = (unsigned char)(0x80 | ((cp >> 12) & 0x3F)); - buf[pos++] = (unsigned char)(0x80 | ((cp >> 6) & 0x3F)); - buf[pos++] = (unsigned char)(0x80 | (cp & 0x3F)); - } - else if (u1 >= 0xDC00 && u1 <= 0xDFFF) - { - // 单独低代理非法 - free(buf); - return 0; - } - else - { - // BMP 码点 → UTF-8 - if (u1 <= 0x007F) { buf[pos++] = (unsigned char)u1; } - else if (u1 <= 0x07FF) - { - buf[pos++] = (unsigned char)(0xC0 | ((u1 >> 6) & 0x1F)); - buf[pos++] = (unsigned char)(0x80 | (u1 & 0x3F)); - } - else - { - buf[pos++] = (unsigned char)(0xE0 | ((u1 >> 12) & 0x0F)); - buf[pos++] = (unsigned char)(0x80 | ((u1 >> 6) & 0x3F)); - buf[pos++] = (unsigned char)(0x80 | (u1 & 0x3F)); - } - } - break; - } - default: - // 非法转义 - free(buf); - return 0; - } - } - else - { - // 原始字节:直接拷贝(假设输入整体是合法 UTF-8) - buf[pos++] = ch; - } - - if (pos + 8 > cap) - { - cap *= 2; - unsigned char *nb = (unsigned char *)realloc(buf, cap); - if (!nb) - { - free(buf); - return 0; - } - buf = nb; - } - } - - *out = buf; - *out_len = pos; - return 1; -} - -// 比较:规范化两侧为 UTF-8 字节序列,再 memcmp -static int json_string_equal_semantic(const char *a, int32_t a_len, const char *b, int32_t b_len) -{ - unsigned char *na = NULL, *nb = NULL; - int32_t nla = 0, nlb = 0; - - if (!normalize_json_string(a, a_len, &na, &nla)) { return 0; } - if (!normalize_json_string(b, b_len, &nb, &nlb)) - { - free(na); - return 0; - } - - int eq = (nla == nlb) && (memcmp(na, nb, nla) == 0); - free(na); - free(nb); - return eq; -} - -/* 去空白 */ -static void trim(const char **s, int32_t *len) -{ - const char *p = *s; - int32_t n = *len; - while (n && isspace((unsigned char)*p)) - { - p++; - n--; - } - while (n && isspace((unsigned char)p[n - 1])) { n--; } - *s = p; - *len = n; -} - -/* 是否是带双引号的字符串值 */ -static int is_quoted_string(const char *s, int32_t len) { return len >= 2 && s[0] == '\"' && s[len - 1] == '\"'; } - -/* 值级语义比较:字符串(去引号并 normalize)、数字(含科学计数法)、布尔、null */ -static int json_scalar_equal(const char *a, int32_t a_len, const char *b, int32_t b_len) -{ - trim(&a, &a_len); - trim(&b, &b_len); - - /* 字符串:去掉引号后做转义规范化再比较字节 */ - if (is_quoted_string(a, a_len) && is_quoted_string(b, b_len)) - { - const char *as = a + 1; - int32_t al = a_len - 2; - const char *bs = b + 1; - int32_t bl = b_len - 2; - - unsigned char *na = NULL, *nb = NULL; - int32_t nla = 0, nlb = 0; - if (!normalize_json_string(as, al, &na, &nla)) { return 0; } - if (!normalize_json_string(bs, bl, &nb, &nlb)) - { - free(na); - return 0; - } - - int eq = (nla == nlb) && (memcmp(na, nb, nla) == 0); - free(na); - free(nb); - return eq; - } - - /* 布尔 / null */ - if (a_len == 4 && strncmp(a, "true", 4) == 0 && b_len == 4 && strncmp(b, "true", 4) == 0) { return 1; } - if (a_len == 5 && strncmp(a, "false", 5) == 0 && b_len == 5 && strncmp(b, "false", 5) == 0) { return 1; } - if (a_len == 4 && strncmp(a, "null", 4) == 0 && b_len == 4 && strncmp(b, "null", 4) == 0) { return 1; } - - /* 数字:用 strtod 支持科学计数法,最后一字节必须到达字符串末尾(避免部分解析) */ - { - char *endA = NULL, *endB = NULL; - - /* 拷贝到以 NUL 结尾的缓冲,避免 strtod 依赖外部长度 */ - char *bufA = (char *)malloc(a_len + 1); - char *bufB = (char *)malloc(b_len + 1); - if (!bufA || !bufB) - { - free(bufA); - free(bufB); - return 0; - } - - memcpy(bufA, a, a_len); - bufA[a_len] = '\0'; - memcpy(bufB, b, b_len); - bufB[b_len] = '\0'; - - double va = strtod(bufA, &endA); - double vb = strtod(bufB, &endB); - - int okA = (endA && *endA == '\0'); - int okB = (endB && *endB == '\0'); - - free(bufA); - free(bufB); - - if (okA && okB) - { - /* 直接相等(包含 -0 与 0)或允许极小误差 */ - if (va == vb) { return 1; } - if (fabs(va - vb) < 1e-15) { return 1; } - return 0; - } - } - - /* 其余作为纯文本兜底比较 */ - return (a_len == b_len) && (memcmp(a, b, a_len) == 0); -} - -/* 提取一元素数组的唯一元素;若不是一元素数组返回 0 */ -static int extract_single_array_element(const char *s, int32_t len, const char **elem, int32_t *elem_len) -{ - trim(&s, &len); - if (len < 2 || s[0] != '[' || s[len - 1] != ']') { return 0; } - - const char *p = s + 1; - int32_t n = len - 2; - - /* 去前后空白 */ - trim(&p, &n); - if (n == 0) { return 0; /* 空数组 */ } - - /* 扫描逗号,确保只有一个元素(字符串中的逗号不算) */ - int in_str = 0; - int escape = 0; - for (int32_t i = 0; i < n; i++) - { - char c = p[i]; - if (in_str) - { - if (escape) { escape = 0; } - else if (c == '\\') { escape = 1; } - else if (c == '\"') { in_str = 0; } - } - else - { - if (c == '\"') { in_str = 1; } - else if (c == ',') { return 0; /* 多元素数组 */ } - } - } - - /* 去尾部空白 */ - const char *q = p + n; - while (n && isspace((unsigned char)q[-1])) - { - q--; - n--; - } - - *elem = p; - *elem_len = n; - return 1; -} - -/* 顶层比较:若两侧都是一元素数组则剥离后比较;否则直接按值级比较 */ -static int json_value_equal(const char *a, int32_t a_len, const char *b, int32_t b_len) -{ - const char *ae = NULL, *be = NULL; - int32_t ale = 0, ble = 0; - - if (extract_single_array_element(a, a_len, &ae, &ale) && extract_single_array_element(b, b_len, &be, &ble)) - { - return json_scalar_equal(ae, ale, be, ble); - } - - return json_scalar_equal(a, a_len, b, b_len); -} - -static void checkadjfladjfl(char *data, uint32_t len, char *str, uint32_t strLen, uint32_t *alksdjfCOunt) -{ - if (0 != strcmp(data, str)) - { - // data/str 是去掉两端引号后的 JSON 字符串内容,并且有长度 - if (!json_value_equal(data, len, str, strLen)) - { - (*alksdjfCOunt)++; - // 打印时避免 %s,被 NUL 截断;可以打印十六进制 - printf("%d 数据不完全一致 -- 原始: %s -- 序列化: %s\n", *alksdjfCOunt, data, str); - } - } -} - -/** - * @brief RyanJson 测试程序 - * - * @param fileName - * @param data - * @param len - * @return int - */ -static int RyanJsonParseData(char *fileName, char *data, uint32_t len) -{ - - if (strcmp(fileName, "n_structure_100000_opening_arrays.json") == 0 || - strcmp(fileName, "n_structure_open_array_object.json") == 0 || strcmp(fileName, "n_structure_100000_opening_arrays.json") == 0) - { - return -1; - } - // printf("开始解析: %s\r\n", fileName); - RyanJson_t json = RyanJsonParseOptions(data, len, RyanJsonTrue, NULL); - if (NULL == json) { return -1; } - -#ifdef PrintfStrCmpEnable - int32_t strLen = 0; - char *str = RyanJsonPrint(json, 60, RyanJsonFalse, &strLen); - if (NULL == str) - { - printf("反序列化失败: [%s]\n", data); - goto err; - } - - RyanJsonMinify(data, len); - static uint32_t alksdjfCOunt = 0; - checkadjfladjfl(data, len, str, strLen, &alksdjfCOunt); - - RyanJsonFree(str); -#endif - - RyanJsonDelete(json); - return 0; - -err: - RyanJsonDelete(json); - return -1; -} - -/** - * @brief cJson测试程序 - * - * @param fileName - * @param data - * @param len - * @return int - */ -static int cJSONParseData(char *fileName, char *data, uint32_t len) -{ - - if (strcmp(fileName, "n_structure_100000_opening_arrays.json") == 0 || - strcmp(fileName, "n_structure_open_array_object.json") == 0 || strcmp(fileName, "n_structure_100000_opening_arrays.json") == 0) - { - return -1; - } - - cJSON *json = cJSON_ParseWithLengthOpts(data, len + sizeof(""), NULL, RyanJsonTrue); - if (NULL == json) { return -1; } - -#ifdef PrintfStrCmpEnable - char *str = cJSON_PrintBuffered(json, 60, RyanJsonFalse); - if (NULL == str) - { - printf("反序列化失败: [%s]\n", data); - goto err; - } - - cJSON_Minify(data); - static uint32_t alksdjfCOunt = 0; - checkadjfladjfl(data, len, str, strlen(str), &alksdjfCOunt); - - cJSON_free(str); -#endif - - cJSON_Delete(json); - return 0; -err: - cJSON_Delete(json); - return -1; -} - -/** - * @brief cJson测试程序 - * - * @param fileName - * @param data - * @param len - * @return int - */ -static int yyjsonParseData(char *fileName, char *data, uint32_t len) -{ - if (strcmp(fileName, "n_structure_100000_opening_arrays.json") == 0 || - strcmp(fileName, "n_structure_open_array_object.json") == 0 || strcmp(fileName, "n_structure_100000_opening_arrays.json") == 0) - { - return -1; - } - - yyjson_doc *doc = yyjson_read(data, len, 0); - if (NULL == doc) { return -1; } - -#ifdef PrintfStrCmpEnable - char *str = yyjson_write(doc, 0, NULL); - if (NULL == str) - { - printf("反序列化失败: [%s]\n", data); - goto err; - } - - cJSON_Minify(data); - static uint32_t alksdjfCOunt = 0; - checkadjfladjfl(data, len, str, strlen(str), &alksdjfCOunt); - - free(str); -#endif - - yyjson_doc_free(doc); - return 0; -err: - yyjson_doc_free(doc); - return -1; -} - -// RFC 8259 JSON Test Suite -// https://github.com/nst/JSONTestSuite -RyanJsonBool_e RFC8259JsonTest(void) -{ - int result = 0; - RyanJsonInitHooks(v_malloc, v_free, v_realloc); - - cJSON_Hooks hooks = {.malloc_fn = v_malloc, .free_fn = v_free}; - cJSON_InitHooks(&hooks); - - printf("开始 RFC 8259 JSON 测试"); - - printf("\r\n--------------------------- RFC8259 RyanJson --------------------------\r\n"); - result = testFile("../../../../test//RFC8259JsonData", RyanJsonParseData); - if (0 != result) - { - printf("%s:%d RyanJson RFC8259JsonTest fail\r\n", __FILE__, __LINE__); - goto err; - } - - printf("\r\n--------------------------- RFC8259 cJSON --------------------------\r\n"); - result = testFile("../../../../test//RFC8259JsonData", cJSONParseData); - if (0 != result) - { - printf("%s:%d cJSON RFC8259JsonTest fail\r\n", __FILE__, __LINE__); - goto err; - } - - printf("\r\n--------------------------- RFC8259 yyjson --------------------------\r\n"); - result = testFile("../../../../test//RFC8259JsonData", yyjsonParseData); - if (0 != result) - { - printf("%s:%d yyjson RFC8259JsonTest fail\r\n", __FILE__, __LINE__); - goto err; - } - - displayMem(); - return RyanJsonTrue; - -err: - displayMem(); - return RyanJsonFalse; -} diff --git a/test/RyanJsonTest.c b/test/RyanJsonTest.c deleted file mode 100644 index ce3d29b..0000000 --- a/test/RyanJsonTest.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "RyanJsonTest.h" - -static void printfTitle(char *title) -{ - printf("\r\n"); - printf("\r\n"); - printf("*****************************************************************************\r\n"); - printf("*************************** %s **************************\r\n", title); - printf("*****************************************************************************\r\n"); -} - -#ifndef isEnableFuzzer -extern void printJsonDebug(RyanJson_t json); -int main(void) -{ - - RyanJsonBool_e result = RyanJsonFalse; - RyanJsonInitHooks(v_malloc, v_free, v_realloc); - - for (uint32_t i = 0; i < 1; i++) - { - - char *str = NULL; - RyanJson_t jsonRoot, item; - - // const char *jsonstr = - // "{\"emoji\":\"\\uD83D\\uDE00\"} "; - // const char *jsonstr = "{\"name\":\"Mash\",\"star\":4,\"hits\":[2,2,1,3]}"; - const char *jsonstr = "[1"; - - // extern int LLVMFuzzerTestOneInput(const char *data, int32_t size); - // LLVMFuzzerTestOneInput(jsonstr, strlen(jsonstr)); - - // 解析json数据 - // jsonRoot = RyanJsonParse(jsonstr); - // if (jsonRoot == NULL) { printf("%s:%d 序列化失败\r\n", __FILE__, __LINE__); } - // else - // { - // uint32_t len = 0; - // str = RyanJsonPrint(jsonRoot, 10, RyanJsonFalse, &len); // 以带格式方式将数据打印出来 - // printf("strLen: %d, data: %s\r\n", len, str); - - // RyanJsonFree(str); - // RyanJsonDelete(jsonRoot); - // } - } - - RyanJsonExample(); - - result = RyanJsonBaseTest(); - if (RyanJsonTrue != result) - { - printf("%s:%d RyanJsonTest fail\r\n", __FILE__, __LINE__); - return -1; - } - - printfTitle("RyanJson / cJSON / yyjson RFC8259标准测试"); - RFC8259JsonTest(); - - printfTitle("RyanJson / cJSON / yyjson 内存对比程序"); - RyanJsonMemoryFootprintTest(); - printf("\r\nok\r\n"); - - displayMem(); - return 0; -} - -#endif // isEnableFuzzer diff --git a/test/baseTest/RyanJsonBaseTest.c b/test/baseTest/RyanJsonBaseTest.c deleted file mode 100644 index 1ce7ba4..0000000 --- a/test/baseTest/RyanJsonBaseTest.c +++ /dev/null @@ -1,93 +0,0 @@ -#include "RyanJsonBaseTest.h" - -static RyanJsonBool_e likeReferenceTest() -{ - - // char *str = NULL; - // char jsonstr[] = - // "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89],\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]}"; - // RyanJson_t json = RyanJsonParse(jsonstr); - // RyanJson_t item = NULL; - - // // RyanJson_t adfasdf = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); - - // // RyanJsonAddItemToObject(json, "test", adfasdf); - - // // 这里做你想做的事,这里我选择打印出来 - // // str = RyanJsonPrint(json, 50, RyanJsonTrue, NULL); - // // printf("item %s \r\n", str); - // // RyanJsonFree(str); - - // for (int i = 0; i < 1; i++) - // { - // // 分离test对象 - // item = RyanJsonDetachByKey(json, "item"); - - // // if (RyanJsonIsKey(item)) - // // RyanJsonFree(RyanJsonGetKey(item)); - - // // RyanJsonFree(item); - // } - - // RyanJsonAddItemToObject(json, "item", item); - - // str = RyanJsonPrint(json, 50, RyanJsonTrue, NULL); - // printf("item %s \r\n", str); - // RyanJsonFree(str); - - // RyanJsonDelete(json); - - return 0; -} - -uint64_t platformUptimeMs(void) -{ - struct timespec ts; - // CLOCK_MONOTONIC: 单调递增,不受系统时间修改影响,适合做耗时统计 - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - -RyanJsonBool_e RyanJsonBaseTest(void) -{ - int32_t result = 0; - RyanJsonInitHooks(v_malloc, v_free, v_realloc); - - uint32_t testRunCount = 0; - uint64_t funcStartMs; -#define runTestWithLogAndTimer(fun) \ - do \ - { \ - testRunCount++; \ - printf("┌── [TEST %d] 开始执行: " #fun "()\r\n", testRunCount); \ - funcStartMs = platformUptimeMs(); \ - result = fun(); \ - printf("└── [TEST %" PRIu32 "] 结束执行: 返回值 = %" PRId32 " %s | 耗时: %" PRIu64 " ms\x1b[0m\r\n\r\n", testRunCount, \ - result, (result == RyanJsonTrue) ? "✅" : "❌", (platformUptimeMs() - funcStartMs)); \ - RyanJsonCheckCodeNoReturn(RyanJsonTrue == result, { goto __exit; }); \ - } while (0) - - runTestWithLogAndTimer(RyanJsonBaseTestChangeJson); // JSON 修改功能的条件覆盖测试 - runTestWithLogAndTimer(RyanJsonBaseTestCompareJson); // 节点比较与一致性验证 - runTestWithLogAndTimer(RyanJsonBaseTestCreateJson); // 节点创建与结构正确性检查 - runTestWithLogAndTimer(RyanJsonBaseTestDeleteJson); // JSON 删除功能的条件覆盖测试 - runTestWithLogAndTimer(RyanJsonBaseTestDetachJson); // 节点分离操作的条件覆盖测试 - runTestWithLogAndTimer(RyanJsonBaseTestDuplicateJson); // 节点复制的深拷贝与浅拷贝验证 - runTestWithLogAndTimer(RyanJsonBaseTestForEachJson); // 节点遍历与迭代稳定性测试 - runTestWithLogAndTimer(RyanJsonBaseTestLoadJson); // JSON 文本解析与加载能力验证 - runTestWithLogAndTimer(RyanJsonBaseTestReplaceJson); // 节点替换功能的条件覆盖测试 - - // result = likeReferenceTest(); // 模仿 引用类型实现 示例 - // if (0 != result) - // { - // printf("%s:%d loadJsonTest fail\r\n", __FILE__, __LINE__); - // return RyanJsonFalse; - // } - - displayMem(); - return RyanJsonTrue; - -__exit: - displayMem(); - return RyanJsonFalse; -} diff --git a/test/baseTest/RyanJsonBaseTest.h b/test/baseTest/RyanJsonBaseTest.h deleted file mode 100644 index 77eb106..0000000 --- a/test/baseTest/RyanJsonBaseTest.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef __RyanJsonBaseTest__ -#define __RyanJsonBaseTest__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include -#include - -#include "RyanJson.h" -#include "RyanJsonUtils.h" -#include "cJSON.h" -#include "valloc.h" -#include "RyanJsonTest.h" -#define jsonLog(fmt, ...) printf("%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__) - -// 定义枚举类型 - -// 定义结构体类型 - -/* extern variables-----------------------------------------------------------*/ - -extern RyanJsonBool_e compare_double(double a, double b); -extern void printJsonDebug(RyanJson_t json); -extern RyanJsonBool_e rootNodeCheckTest(RyanJson_t json); -extern RyanJsonBool_e itemNodeCheckTest(RyanJson_t json); -extern RyanJsonBool_e arrayNodeCheckTest(RyanJson_t json); -extern RyanJsonBool_e arrayItemNodeCheckTest(RyanJson_t json); -extern RyanJsonBool_e RyanJsonBaseTestCheckRoot(RyanJson_t pJson); - -extern RyanJsonBool_e RyanJsonBaseTestChangeJson(void); -extern RyanJsonBool_e RyanJsonBaseTestCompareJson(void); -extern RyanJsonBool_e RyanJsonBaseTestCreateJson(void); -extern RyanJsonBool_e RyanJsonBaseTestDeleteJson(void); -extern RyanJsonBool_e RyanJsonBaseTestDetachJson(void); -extern RyanJsonBool_e RyanJsonBaseTestDuplicateJson(void); -extern RyanJsonBool_e RyanJsonBaseTestForEachJson(void); -extern RyanJsonBool_e RyanJsonBaseTestLoadJson(void); -extern RyanJsonBool_e RyanJsonBaseTestReplaceJson(void); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/test/baseTest/RyanJsonBaseTestChangeJson.c b/test/baseTest/RyanJsonBaseTestChangeJson.c deleted file mode 100644 index a1e368a..0000000 --- a/test/baseTest/RyanJsonBaseTestChangeJson.c +++ /dev/null @@ -1,103 +0,0 @@ -#include "RyanJsonBaseTest.h" - -/* --------------------------------------------------------------------- */ - -RyanJsonBool_e RyanJsonBaseTestChangeJson(void) -{ - char jsonstr[] = - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"]," - "\"array\":[16,16.89,\"hello\",true,false,null]," - "\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]," - "\"string2222\":\"hello\"}"; - - RyanJson_t json = RyanJsonParse(jsonstr); - RyanJsonCheckReturnFalse(NULL != json); - - /** - * @brief 修改基本类型 - */ - RyanJsonChangeIntValue(RyanJsonGetObjectToKey(json, "inter"), 20); - RyanJsonCheckCode(RyanJsonIsInt(RyanJsonGetObjectToKey(json, "inter")) && - 20 == RyanJsonGetIntValue(RyanJsonGetObjectToKey(json, "inter")), - { goto err; }); - - RyanJsonChangeDoubleValue(RyanJsonGetObjectToKey(json, "double"), 20.89); - RyanJsonCheckCode(RyanJsonIsDouble(RyanJsonGetObjectToKey(json, "double")) && - compare_double(RyanJsonGetDoubleValue(RyanJsonGetObjectToKey(json, "double")), 20.89), - { goto err; }); - - RyanJsonChangeStringValue(RyanJsonGetObjectToKey(json, "string"), "world"); - RyanJsonCheckCode(RyanJsonIsString(RyanJsonGetObjectToKey(json, "string")) && - strcmp(RyanJsonGetStringValue(RyanJsonGetObjectToKey(json, "string")), "world") == 0, - { goto err; }); - - RyanJsonChangeBoolValue(RyanJsonGetObjectToKey(json, "boolTrue"), RyanJsonFalse); - RyanJsonCheckCode(RyanJsonIsBool(RyanJsonGetObjectToKey(json, "boolTrue")) && - RyanJsonGetBoolValue(RyanJsonGetObjectToKey(json, "boolTrue")) == RyanJsonFalse, - { goto err; }); - - RyanJsonChangeBoolValue(RyanJsonGetObjectToKey(json, "boolFalse"), RyanJsonTrue); - RyanJsonCheckCode(RyanJsonIsBool(RyanJsonGetObjectToKey(json, "boolFalse")) && - RyanJsonGetBoolValue(RyanJsonGetObjectToKey(json, "boolFalse")) == RyanJsonTrue, - { goto err; }); - - /** - * @brief 修改数组元素 (arrayInt) - */ - RyanJsonChangeIntValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 0), 99); - RyanJsonCheckCode(RyanJsonIsInt(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 0)) && - RyanJsonGetIntValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 0)) == 99, - { goto err; }); - - /** - * @brief 修改数组元素 (arrayDouble) - */ - RyanJsonChangeDoubleValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayDouble"), 1), 99.99); - RyanJsonCheckCode( - RyanJsonIsDouble(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayDouble"), 1)) && - compare_double(RyanJsonGetDoubleValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayDouble"), 1)), - 99.99), - { goto err; }); - - /** - * @brief 修改数组元素 (arrayString) - */ - RyanJsonChangeStringValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayString"), 2), "changedString"); - RyanJsonCheckCode(RyanJsonIsString(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayString"), 2)) && - strcmp(RyanJsonGetStringValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayString"), 2)), - "changedString") == 0, - { goto err; }); - - /** - * @brief 修改嵌套对象 - */ - RyanJsonChangeStringValue(RyanJsonGetObjectToKey(RyanJsonGetObjectToKey(json, "item"), "string"), "nestedWorld"); - RyanJsonCheckCode(RyanJsonIsString(RyanJsonGetObjectToKey(RyanJsonGetObjectToKey(json, "item"), "string")) && - strcmp(RyanJsonGetStringValue(RyanJsonGetObjectToKey(RyanJsonGetObjectToKey(json, "item"), "string")), - "nestedWorld") == 0, - { goto err; }); - - /** - * @brief 修改数组对象中的字段 (arrayItem[0].inter -> 123) - */ - RyanJsonChangeIntValue(RyanJsonGetObjectToKey(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 0), "inter"), - 123); - RyanJsonCheckCode( - RyanJsonIsInt(RyanJsonGetObjectToKey(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 0), "inter")) && - RyanJsonGetIntValue(RyanJsonGetObjectToKey(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 0), - "inter")) == 123, - { goto err; }); - - char *str = RyanJsonPrint(json, 1024, RyanJsonTrue, NULL); - RyanJsonFree(str); - RyanJsonDelete(json); - return RyanJsonTrue; - -err: - RyanJsonDelete(json); - return RyanJsonFalse; -} diff --git a/test/baseTest/RyanJsonBaseTestCompareJson.c b/test/baseTest/RyanJsonBaseTestCompareJson.c deleted file mode 100644 index f5cb8fa..0000000 --- a/test/baseTest/RyanJsonBaseTestCompareJson.c +++ /dev/null @@ -1,158 +0,0 @@ -#include "RyanJsonBaseTest.h" -/* --------------------------------------------------------------------- */ - -RyanJsonBool_e RyanJsonBaseTestCompareJson(void) -{ - char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" - "{\"inter\":16,\"double\":16." - "89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," - "16.89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null}]}"; - - RyanJson_t json = RyanJsonParse(jsonstr); - RyanJson_t json2 = RyanJsonParse(jsonstr); - - // 比较函数 - RyanJsonCheckCode(RyanJsonTrue == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddStringToObject(json2, "test", "hello"); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddIntToObject(json2, "test", 1); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddDoubleToObject(json2, "test", 2.0); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddBoolToObject(json2, "test", RyanJsonTrue); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddNullToObject(json2, "test"); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddIntToArray(RyanJsonGetObjectToKey(json2, "arrayInt"), 2); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddDoubleToArray(RyanJsonGetObjectToKey(json2, "arrayDouble"), 2.0); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddStringToArray(RyanJsonGetObjectToKey(json2, "arrayString"), "hello"); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonAddItemToArray(RyanJsonGetObjectToKey(json2, "arrayItem"), RyanJsonCreateString("test", "hello")); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeKey(RyanJsonGetObjectToKey(json2, "inter"), "int2"); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeIntValue(RyanJsonGetObjectToKey(json2, "inter"), 17); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeDoubleValue(RyanJsonGetObjectToKey(json2, "double"), 20.89); - if (RyanJsonFalse != RyanJsonCompare(json, json2)) - { - printf("%s:%d 解析失败\r\n", __FILE__, __LINE__); - goto err; - } - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonDelete(RyanJsonDetachByKey(json2, "double")); - RyanJsonAddIntToObject(json2, "double", 20); // 改为int - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeStringValue(RyanJsonGetObjectToKey(json2, "string"), "49"); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeBoolValue(RyanJsonGetObjectToKey(json2, "boolTrue"), RyanJsonFalse); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeBoolValue(RyanJsonGetObjectToKey(json2, "item", "boolTrue"), RyanJsonFalse); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeIntValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "arrayInt"), 0), 17); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeDoubleValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "arrayDouble"), 0), 20.89); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeStringValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "arrayString"), 0), "20.89"); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeIntValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "array"), 0), 17); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonChangeIntValue(RyanJsonGetObjectToKey(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "arrayItem"), 0), "inter"), - 17); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonDeleteByKey(json2, "arrayItem"); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonDeleteByIndex(RyanJsonGetObjectToKey(json2, "arrayInt"), 2); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json2); - json2 = RyanJsonParse(jsonstr); - RyanJsonDeleteByIndex(RyanJsonGetObjectToKey(json2, "arrayItem"), 0); - RyanJsonCheckCode(RyanJsonFalse == RyanJsonCompare(json, json2), { goto err; }); - - RyanJsonDelete(json); - RyanJsonDelete(json2); - return RyanJsonTrue; - -err: - RyanJsonDelete(json); - RyanJsonDelete(json2); - return RyanJsonFalse; -} diff --git a/test/baseTest/RyanJsonBaseTestCreateJson.c b/test/baseTest/RyanJsonBaseTestCreateJson.c deleted file mode 100644 index 7767274..0000000 --- a/test/baseTest/RyanJsonBaseTestCreateJson.c +++ /dev/null @@ -1,86 +0,0 @@ - -#include "RyanJsonBaseTest.h" - -RyanJsonBool_e RyanJsonBaseTestCreateJson(void) -{ - RyanJson_t jsonRoot, item; - - // 对象生成测试 - jsonRoot = RyanJsonCreateObject(); - RyanJsonAddIntToObject(jsonRoot, "inter", 16); - RyanJsonAddDoubleToObject(jsonRoot, "double", 16.89); - RyanJsonAddStringToObject(jsonRoot, "string", "hello"); - RyanJsonAddBoolToObject(jsonRoot, "boolTrue", RyanJsonTrue); - RyanJsonAddBoolToObject(jsonRoot, "boolFalse", RyanJsonFalse); - RyanJsonAddNullToObject(jsonRoot, "null"); - - /** - * @brief 对象添加测试 - * - */ - item = RyanJsonCreateObject(); - RyanJsonAddIntToObject(item, "inter", 16); - RyanJsonAddDoubleToObject(item, "double", 16.89); - RyanJsonAddStringToObject(item, "string", "hello"); - RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); - RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); - RyanJsonAddNullToObject(item, "null"); - RyanJsonAddItemToObject(jsonRoot, "item", item); - - /** - * @brief 数组添加测试 - * - */ - int arrayInt[] = {16, 16, 16, 16, 16}; - RyanJsonAddItemToObject(jsonRoot, "arrayInt", RyanJsonCreateIntArray(arrayInt, sizeof(arrayInt) / sizeof(arrayInt[0]))); - - double arrayDouble[] = {16.89, 16.89, 16.89, 16.89, 16.89}; - RyanJsonAddItemToObject(jsonRoot, "arrayDouble", - RyanJsonCreateDoubleArray(arrayDouble, sizeof(arrayDouble) / sizeof(arrayDouble[0]))); - - const char *arrayString[] = {"hello", "hello", "hello", "hello", "hello"}; - RyanJsonAddItemToObject(jsonRoot, "arrayString", - RyanJsonCreateStringArray(arrayString, sizeof(arrayString) / sizeof(arrayString[0]))); - - RyanJson_t array = RyanJsonCreateArray(); - RyanJsonAddIntToArray(array, 16); - RyanJsonAddDoubleToArray(array, 16.89); - RyanJsonAddStringToArray(array, "hello"); - RyanJsonAddBoolToArray(array, RyanJsonTrue); - RyanJsonAddBoolToArray(array, RyanJsonFalse); - RyanJsonAddNullToArray(array); - RyanJsonAddItemToObject(jsonRoot, "array", array); - - /** - * @brief 对象数组测试 - * - */ - RyanJson_t arrayItem = RyanJsonCreateArray(); - item = RyanJsonCreateObject(); - RyanJsonAddIntToObject(item, "inter", 16); - RyanJsonAddDoubleToObject(item, "double", 16.89); - RyanJsonAddStringToObject(item, "string", "hello"); - RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); - RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); - RyanJsonAddNullToObject(item, "null"); - RyanJsonAddItemToObject(arrayItem, "item", item); - - item = RyanJsonCreateObject(); - RyanJsonAddIntToObject(item, "inter", 16); - RyanJsonAddDoubleToObject(item, "double", 16.89); - RyanJsonAddStringToObject(item, "string", "hello"); - RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); - RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); - RyanJsonAddNullToObject(item, "null"); - RyanJsonAddItemToObject(arrayItem, "item", item); - - RyanJsonAddItemToObject(jsonRoot, "arrayItem", arrayItem); - - RyanJsonCheckCode(RyanJsonTrue == RyanJsonBaseTestCheckRoot(jsonRoot), { - RyanJsonDelete(jsonRoot); - return RyanJsonFalse; - }); - RyanJsonDelete(jsonRoot); - - return RyanJsonTrue; -} diff --git a/test/baseTest/RyanJsonBaseTestDeleteJson.c b/test/baseTest/RyanJsonBaseTestDeleteJson.c deleted file mode 100644 index f6f894b..0000000 --- a/test/baseTest/RyanJsonBaseTestDeleteJson.c +++ /dev/null @@ -1,112 +0,0 @@ -#include "RyanJsonBaseTest.h" - -/* --------------------------------------------------------------------- */ - -RyanJsonBool_e RyanJsonBaseTestDeleteJson(void) -{ - // 保持原始 jsonStr,不做修改 - char jsonstr[] = - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"]," - "\"array\":[16,16.89,\"hello\",true,false,null]," - "\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]," - "\"string2222\":\"hello\"}"; - - RyanJson_t json = RyanJsonParse(jsonstr); - RyanJsonCheckReturnFalse(NULL != json); - - /** - * @brief 场景 1:删除对象中的节点(头、中、尾) - */ - { - // 删除中间节点 (double) - RyanJsonDeleteByKey(json, "double"); - RyanJsonCheckCode(NULL == RyanJsonGetObjectToKey(json, "double"), { goto err; }); - - // 删除头部节点 (inter) - RyanJsonDeleteByIndex(json, 0); - RyanJsonCheckCode(NULL == RyanJsonGetObjectToKey(json, "inter"), { goto err; }); - - // 删除尾部节点 (string2222) - uint32_t lastIndex = RyanJsonGetSize(json) - 1; - RyanJsonDeleteByIndex(json, lastIndex); - RyanJsonCheckCode(NULL == RyanJsonGetObjectToKey(json, "string2222"), { goto err; }); - } - - /** - * @brief 场景 2:删除数组中的元素(arrayInt) - */ - { - RyanJson_t array = RyanJsonGetObjectToKey(json, "arrayInt"); - - // 删除数组首位 - RyanJsonDeleteByIndex(array, 0); - RyanJsonCheckCode(RyanJsonGetSize(array) == 4, { goto err; }); - - // 删除数组中间元素 - RyanJsonDeleteByIndex(array, 1); - RyanJsonCheckCode(RyanJsonGetSize(array) == 3, { goto err; }); - - // 删除数组尾部元素 - uint32_t lastIndex = RyanJsonGetSize(array) - 1; - RyanJsonDeleteByIndex(array, lastIndex); - RyanJsonCheckCode(RyanJsonGetSize(array) == 2, { goto err; }); - } - - /** - * @brief 场景 3:深层嵌套删除(item) - */ - { - RyanJsonDeleteByKey(json, "item"); - RyanJsonCheckCode(NULL == RyanJsonGetObjectToKey(json, "item"), { goto err; }); - } - - /** - * @brief 场景 4:数组对象元素删除(arrayItem) - */ - { - RyanJson_t arrObj = RyanJsonGetObjectToKey(json, "arrayItem"); - - // 删除第一个对象 - RyanJsonDeleteByIndex(arrObj, 0); - RyanJsonCheckCode(RyanJsonGetSize(arrObj) == 1, { goto err; }); - - // 删除最后一个对象 - RyanJsonDeleteByIndex(arrObj, 0); - RyanJsonCheckCode(RyanJsonGetSize(arrObj) == 0, { goto err; }); - } - - /** - * @brief 场景 5:特殊类型删除(null / bool) - */ - { - RyanJsonDeleteByKey(json, "null"); - RyanJsonCheckCode(NULL == RyanJsonGetObjectToKey(json, "null"), { goto err; }); - - RyanJsonDeleteByKey(json, "boolTrue"); - RyanJsonCheckCode(NULL == RyanJsonGetObjectToKey(json, "boolTrue"), { goto err; }); - - RyanJsonDeleteByKey(json, "boolFalse"); - RyanJsonCheckCode(NULL == RyanJsonGetObjectToKey(json, "boolFalse"), { goto err; }); - } - - /** - * @brief 场景 6:异常路径覆盖(健壮性) - */ - { - RyanJsonDeleteByKey(json, "non_exist"); // 删除不存在的 Key - RyanJsonDeleteByIndex(NULL, 0); // 在 NULL 上操作 - } - - char *str = RyanJsonPrint(json, 1024, RyanJsonTrue, NULL); - RyanJsonFree(str); - RyanJsonDelete(json); - return RyanJsonTrue; - -err: - RyanJsonDelete(json); - return RyanJsonFalse; -} diff --git a/test/baseTest/RyanJsonBaseTestDetachJson.c b/test/baseTest/RyanJsonBaseTestDetachJson.c deleted file mode 100644 index 503621c..0000000 --- a/test/baseTest/RyanJsonBaseTestDetachJson.c +++ /dev/null @@ -1,217 +0,0 @@ -#include "RyanJsonBaseTest.h" - -/* --------------------------------------------------------------------- */ - -RyanJsonBool_e RyanJsonBaseTestDetachJson(void) -{ - char jsonstr[] = - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"]," - "\"array\":[16,16.89,\"hello\",true,false,null]," - "\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{" - "\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]," - "\"string2222\":\"hello\"}"; - - RyanJson_t json = RyanJsonParse(jsonstr); - RyanJsonCheckReturnFalse(NULL != json); - - /** - * @brief 对象子项分离测试(头、中、尾) - */ - { - RyanJson_t json2 = RyanJsonParse(jsonstr); - - // 头部 (第一个 key: inter) - RyanJsonDelete(RyanJsonDetachByIndex(json, 0)); - if (RyanJsonGetObjectToKey(json, "inter")) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - // 中间 (double) - RyanJsonDelete(RyanJsonDetachByKey(json, "double")); - if (RyanJsonGetObjectToKey(json, "double")) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - // 尾部 (最后一个 key: string2222) - uint32_t lastIndex = RyanJsonGetSize(json) - 1; - RyanJsonDelete(RyanJsonDetachByIndex(json, lastIndex)); - if (RyanJsonGetObjectToKey(json, "string2222")) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(json2); - } - - /** - * @brief 数组元素分离测试 (arrayInt 头、中、尾) - */ - { - RyanJson_t arr = RyanJsonGetObjectByKey(json, "arrayInt"); - RyanJson_t json2 = RyanJsonParse(jsonstr); - - uint32_t jsonSize = RyanJsonGetSize(arr); - // 头部 - RyanJsonDelete(RyanJsonDetachByIndex(arr, 0)); - if (jsonSize == RyanJsonGetSize(arr)) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - // 中间 - jsonSize = RyanJsonGetSize(arr); - RyanJsonDelete(RyanJsonDetachByIndex(arr, 2)); - if (jsonSize == RyanJsonGetSize(arr)) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - // 尾部 - uint32_t lastIndex = RyanJsonGetSize(arr) - 1; - RyanJsonDelete(RyanJsonDetachByIndex(arr, lastIndex)); - if (NULL != RyanJsonGetObjectToIndex(arr, lastIndex)) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(json2); - } - - /** - * @brief 数组元素分离测试 (arrayDouble 头、中、尾) - */ - { - RyanJson_t arr = RyanJsonGetObjectByKey(json, "arrayDouble"); - RyanJson_t json2 = RyanJsonParse(jsonstr); - - RyanJsonDelete(RyanJsonDetachByIndex(arr, 0)); - if (RyanJsonGetObjectToIndex(arr, 0) == NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(RyanJsonDetachByIndex(arr, 2)); - if (RyanJsonGetObjectToIndex(arr, 2) == NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - uint32_t lastIndex = RyanJsonGetSize(arr) - 1; - RyanJsonDelete(RyanJsonDetachByIndex(arr, lastIndex)); - if (RyanJsonGetObjectToIndex(arr, lastIndex) != NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(json2); - } - - /** - * @brief 数组元素分离测试 (arrayString 头、中、尾) - */ - { - RyanJson_t arr = RyanJsonGetObjectByKey(json, "arrayString"); - RyanJson_t json2 = RyanJsonParse(jsonstr); - - RyanJsonDelete(RyanJsonDetachByIndex(arr, 0)); - if (RyanJsonGetObjectToIndex(arr, 0) == NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(RyanJsonDetachByIndex(arr, 2)); - if (RyanJsonGetObjectToIndex(arr, 2) == NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - uint32_t lastIndex = RyanJsonGetSize(arr) - 1; - RyanJsonDelete(RyanJsonDetachByIndex(arr, lastIndex)); - if (RyanJsonGetObjectToIndex(arr, lastIndex) != NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(json2); - } - - /** - * @brief 嵌套对象分离测试 (item) - */ - { - RyanJson_t json2 = RyanJsonParse(jsonstr); - RyanJsonDelete(RyanJsonDetachByKey(json, "item")); - if (RyanJsonGetObjectToKey(json, "item")) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - RyanJsonDelete(json2); - } - - /** - * @brief 数组对象元素分离测试 (arrayItem 头、中、尾) - */ - { - RyanJson_t arr = RyanJsonGetObjectByKey(json, "arrayItem"); - RyanJson_t json2 = RyanJsonParse(jsonstr); - - RyanJsonDelete(RyanJsonDetachByIndex(arr, 0)); - if (RyanJsonGetObjectToIndex(arr, 0) == NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(RyanJsonDetachByIndex(arr, 1)); - if (RyanJsonGetObjectToIndex(arr, 1) == NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - uint32_t lastIndex = RyanJsonGetSize(arr) - 1; - RyanJsonDelete(RyanJsonDetachByIndex(arr, lastIndex)); - if (RyanJsonGetObjectToIndex(arr, lastIndex) != NULL) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(json2); - } - - /** - * @brief 特殊类型分离测试(null / bool) - */ - { - RyanJson_t json2 = RyanJsonParse(jsonstr); - - RyanJsonDelete(RyanJsonDetachByKey(json, "null")); - if (RyanJsonGetObjectToKey(json, "null")) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(RyanJsonDetachByKey(json, "boolTrue")); - if (RyanJsonGetObjectToKey(json, "boolTrue")) - { - RyanJsonCheckCode(NULL, { goto err; }); - } - - RyanJsonDelete(json2); - } - - char *str = RyanJsonPrint(json, 1024, RyanJsonTrue, NULL); - RyanJsonFree(str); - RyanJsonDelete(json); - return RyanJsonTrue; - -err: - RyanJsonDelete(json); - return RyanJsonFalse; -} diff --git a/test/baseTest/RyanJsonBaseTestDuplicateJson.c b/test/baseTest/RyanJsonBaseTestDuplicateJson.c deleted file mode 100644 index 8ae01cd..0000000 --- a/test/baseTest/RyanJsonBaseTestDuplicateJson.c +++ /dev/null @@ -1,139 +0,0 @@ - -#include "RyanJsonBaseTest.h" - -RyanJsonBool_e RyanJsonBaseTestDuplicateJson(void) -{ - RyanJson_t json, dupItem, jsonRoot = NULL; - char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" - "{\"inter\":16,\"double\":16." - "89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," - "16.89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null}]}"; - - /** - * @brief 普通类型 - * - */ - json = RyanJsonParse(jsonstr); - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "inter")); - if (RyanJsonFalse == RyanJsonCompare(dupItem, RyanJsonGetObjectToKey(json, "inter"))) { goto err; } - RyanJsonDelete(dupItem); - - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "inter")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test", "inter"), RyanJsonGetObjectToKey(json, "inter"))) - { - goto err; - } - RyanJsonDelete(RyanJsonDetachByKey(json, "test")); - - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "inter")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test", "inter"), RyanJsonGetObjectToKey(json, "inter"))) - { - goto err; - } - RyanJsonDelete(json); - - json = RyanJsonParse(jsonstr); - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "inter")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test", "inter"), RyanJsonGetObjectToKey(json, "inter"))) - { - goto err; - } - RyanJsonDelete(RyanJsonDetachByKey(json, "test")); - RyanJsonDelete(json); - - /** - * @brief 对象类型 - * - */ - json = RyanJsonParse(jsonstr); - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); - if (RyanJsonFalse == RyanJsonCompare(dupItem, RyanJsonGetObjectToKey(json, "item"))) { goto err; } - RyanJsonDelete(dupItem); - - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "item"))) { goto err; } - RyanJsonDelete(RyanJsonDetachByKey(json, "test")); - - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "item"))) { goto err; } - RyanJsonDelete(json); - - json = RyanJsonParse(jsonstr); - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "item"))) { goto err; } - RyanJsonDelete(RyanJsonDetachByKey(json, "test")); - RyanJsonDelete(json); - - /** - * @brief 数组类型 - * - */ - json = RyanJsonParse(jsonstr); - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "arrayItem")); - if (RyanJsonFalse == RyanJsonCompare(dupItem, RyanJsonGetObjectToKey(json, "arrayItem"))) { goto err; } - RyanJsonDelete(dupItem); - - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "arrayItem")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "arrayItem"))) { goto err; } - RyanJsonDelete(RyanJsonDetachByKey(json, "test")); - - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "arrayItem")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "arrayItem"))) { goto err; } - RyanJsonDelete(json); - - json = RyanJsonParse(jsonstr); - dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "arrayItem")); - RyanJsonAddItemToObject(json, "test", dupItem); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "arrayItem"))) { goto err; } - RyanJsonDelete(RyanJsonDetachByKey(json, "test")); - RyanJsonDelete(json); - - json = RyanJsonParse(jsonstr); - jsonRoot = RyanJsonCreateObject(); - RyanJsonAddBoolToObject(jsonRoot, "arrayItem", RyanJsonTrue); - int use = 0; - for (uint8_t i = 0; i < 10; i++) - { - dupItem = RyanJsonParse(jsonstr); - RyanJsonReplaceByKey(jsonRoot, "arrayItem", RyanJsonDuplicate(dupItem)); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(jsonRoot, "arrayItem"), dupItem)) { goto err; } - RyanJsonReplaceByKey(json, "arrayItem", RyanJsonDuplicate(RyanJsonGetObjectByKey(dupItem, "item"))); - if (RyanJsonFalse == RyanJsonCompare(RyanJsonGetObjectToKey(json, "arrayItem"), RyanJsonGetObjectByKey(dupItem, "item"))) - { - goto err; - } - RyanJsonDelete(dupItem); - - int newuse = vallocGetUse(); - if (i != 0 && newuse != use) - { - printf("%s:%d 内存泄漏\r\n", __FILE__, __LINE__); - goto err; - } - use = newuse; - } - - RyanJsonDelete(json); - RyanJsonDelete(jsonRoot); - return RyanJsonTrue; - -err: - RyanJsonDelete(json); - RyanJsonDelete(jsonRoot); - return RyanJsonFalse; -} diff --git a/test/baseTest/RyanJsonBaseTestForEachJson.c b/test/baseTest/RyanJsonBaseTestForEachJson.c deleted file mode 100644 index 128b8f4..0000000 --- a/test/baseTest/RyanJsonBaseTestForEachJson.c +++ /dev/null @@ -1,45 +0,0 @@ - -#include "RyanJsonBaseTest.h" - -RyanJsonBool_e RyanJsonBaseTestForEachJson(void) -{ - char *str = NULL; - char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" - "{\"inter\":16,\"double\":16." - "89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," - "16.89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null}]}"; - - RyanJson_t json = RyanJsonParse(jsonstr); - RyanJson_t item = NULL; - RyanJsonArrayForEach(RyanJsonGetObjectToKey(json, "arrayDouble"), item) - { - if (!RyanJsonIsDouble(item) || !compare_double(16.89, RyanJsonGetDoubleValue(item))) { goto err; } - } - - RyanJsonArrayForEach(RyanJsonGetObjectToKey(json, "arrayInt"), item) - { - if (!RyanJsonIsInt(item) || 16 != RyanJsonGetIntValue(item)) { goto err; } - } - - int32_t strLen; - RyanJsonObjectForEach(RyanJsonGetObjectToKey(json, "item"), item) - { - str = RyanJsonPrint(item, 50, RyanJsonTrue, &strLen); - printf("item { %s : %s } %d\r\n", RyanJsonGetKey(item), str, strLen); - RyanJsonFree(str); - } - - RyanJsonDelete(json); - return RyanJsonTrue; - -err: - RyanJsonDelete(json); - return RyanJsonFalse; -} diff --git a/test/baseTest/RyanJsonBaseTestLoadJson.c b/test/baseTest/RyanJsonBaseTestLoadJson.c deleted file mode 100644 index 8597e26..0000000 --- a/test/baseTest/RyanJsonBaseTestLoadJson.c +++ /dev/null @@ -1,278 +0,0 @@ - -#include "RyanJsonBaseTest.h" - -/* --------------------------------------------------------------------- */ - -RyanJsonBool_e RyanJsonBaseTestLoadJson(void) -{ - char *str = NULL; - RyanJson_t json; - char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" - "{\"inter\":16,\"double\":16." - "89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," - "16.89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89,\"string\":" - "\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null}]}"; - - json = RyanJsonParse(jsonstr); - RyanJsonCheckReturnFalse(NULL != json); - - str = RyanJsonPrint(json, 250, RyanJsonFalse, NULL); - RyanJsonCheckCode(0 == strcmp(str, jsonstr), { - RyanJsonFree(str); - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - RyanJsonFree(str); - - RyanJsonCheckCode(RyanJsonTrue == RyanJsonBaseTestCheckRoot(json), { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - RyanJsonDelete(json); - - /** - * @brief 测试序列化错误json结构 - * - */ - // \"inter\":16poi, 无效数字 - json = RyanJsonParse("{\"inter\":16poi,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":" - "16,\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // \"double\":16.8yu9,, 无效浮点数 - json = RyanJsonParse("{\"inter\":16,\"double\":16.8yu9,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // boolTrue 设置为 tru - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":tru,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // boolFalse 设置为 fale - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":fale,\"null\":null," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // null 设置为 nul - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":nul," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // null 设置为 NULL - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":NULL," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // \"inter\":16后面少个, - json = RyanJsonParse("{\"inter\":16\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - // array数组项少一个, - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // \"item:{\"inter\":16,\" 少一个" - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item:{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // \"item\":{\"inter\":16,double\" 少一个" - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // \"item\":{\"inter\":16,\"\"double\" 多一个" - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "\"\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // \"item\":{\"inter\":16\",\"double\" 多一个" - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16\"," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16." - "89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - - // \"arrayInt\":[16,16,16m,16,16] 无效数组数字 - json = RyanJsonParse("{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," - "\"item\":{\"inter\":16," - "\"double\":16.89,\"string\":\"hello\"," - "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16m,16,16],\"arrayDouble\":[16.89," - "16.89,16.89,16.89,16." - "89],\"arrayString\":[\"hello\",\"hello\"," - "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," - "\"double\":16.89," - "\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," - "\"boolFalse\":false," - "\"null\":null}]}"); - RyanJsonCheckCode(NULL == json, { - RyanJsonDelete(json); - return RyanJsonFalse; - }); - return RyanJsonTrue; -} diff --git a/test/baseTest/RyanJsonBaseTestReplaceJson.c b/test/baseTest/RyanJsonBaseTestReplaceJson.c deleted file mode 100644 index c770874..0000000 --- a/test/baseTest/RyanJsonBaseTestReplaceJson.c +++ /dev/null @@ -1,222 +0,0 @@ -#include "RyanJsonBaseTest.h" - -/* --------------------------------------------------------------------- */ - -RyanJsonBool_e RyanJsonBaseTestReplaceJson(void) -{ - char jsonstr[] = - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89]," - "\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"]," - "\"array\":[16,16.89,\"hello\",true,false,null]," - "\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," - "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]," - "\"string2222\":\"hello\"}"; - - RyanJson_t json = RyanJsonParse(jsonstr); - RyanJsonCheckReturnFalse(NULL != json); - - /* ---------------- 保留原有测试(并补充校验) ---------------- */ - - // 数组替换测试:arrayInt 头 - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 0, RyanJsonCreateString(NULL, "arrayInt")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 0); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "arrayInt") == 0, { goto err; }); - } - - // 数组替换测试:arrayInt 尾 - { - RyanJson_t arr = RyanJsonGetObjectToKey(json, "arrayInt"); - uint32_t last = RyanJsonGetSize(arr) - 1; - RyanJsonReplaceByIndex(arr, last, RyanJsonCreateString(NULL, "arrayInt")); - RyanJson_t v = RyanJsonGetObjectToIndex(arr, last); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "arrayInt") == 0, { goto err; }); - } - - // 数组对象替换测试:arrayItem[0] - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 0, RyanJsonCreateString(NULL, "arrayItem")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 0); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "arrayItem") == 0, { goto err; }); - } - - // 数组对象替换测试:arrayItem[1] - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 1, RyanJsonCreateString(NULL, "arrayItem")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 1); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "arrayItem") == 0, { goto err; }); - } - - // 对象字段替换:inter -> 999 - RyanJsonReplaceByKey(json, "inter", RyanJsonCreateInt("inter", 999)); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "inter"); - RyanJsonCheckCode(RyanJsonIsInt(v), { goto err; }); - RyanJsonCheckCode(RyanJsonGetIntValue(v) == 999, { goto err; }); - } - - // 对象字段替换:double -> 123.45 - RyanJsonReplaceByKey(json, "double", RyanJsonCreateDouble("double", 123.45)); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "double"); - RyanJsonCheckCode(RyanJsonIsDouble(v), { goto err; }); - RyanJsonCheckCode(RyanJsonGetDoubleValue(v) == 123.45, { goto err; }); - } - - // 对象字段替换:string -> "newString" - RyanJsonReplaceByKey(json, "string", RyanJsonCreateString("string", "newString")); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "string"); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "newString") == 0, { goto err; }); - } - - // 对象字段替换:boolFalse -> true - RyanJsonReplaceByKey(json, "boolFalse", RyanJsonCreateBool("boolFalse", RyanJsonTrue)); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "boolFalse"); - RyanJsonCheckCode(RyanJsonIsBool(v), { goto err; }); - RyanJsonCheckCode(RyanJsonGetBoolValue(v) == RyanJsonTrue, { goto err; }); - } - - // 数组替换:arrayInt 中间元素 -> "midInt" - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 2, RyanJsonCreateString(NULL, "midInt")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 2); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "midInt") == 0, { goto err; }); - } - - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "arrayString"), 1, RyanJsonCreateString(NULL, "headString")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayString"), 1); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "headString") == 0, { goto err; }); - } - { - RyanJson_t arr = RyanJsonGetObjectToKey(json, "arrayString"); - uint32_t last = RyanJsonGetSize(arr) - 1; - RyanJsonReplaceByIndex(arr, last, RyanJsonCreateString(NULL, "tailString")); - RyanJson_t v = RyanJsonGetObjectToIndex(arr, last); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "tailString") == 0, { goto err; }); - } - - // 数组对象替换:arrayItem 尾部 -> "arrayItemTail" - { - RyanJson_t arr = RyanJsonGetObjectToKey(json, "arrayItem"); - uint32_t last = RyanJsonGetSize(arr) - 1; - RyanJsonReplaceByIndex(arr, last, RyanJsonCreateString(NULL, "arrayItemTail")); - RyanJson_t v = RyanJsonGetObjectToIndex(arr, last); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "arrayItemTail") == 0, { goto err; }); - } - - // 嵌套对象替换:item.inter -> 111 - RyanJsonReplaceByKey(RyanJsonGetObjectToKey(json, "item"), "inter", RyanJsonCreateInt("inter", 111)); - { - RyanJson_t v = RyanJsonGetObjectToKey(RyanJsonGetObjectToKey(json, "item"), "inter"); - RyanJsonCheckCode(RyanJsonIsInt(v), { goto err; }); - RyanJsonCheckCode(RyanJsonGetIntValue(v) == 111, { goto err; }); - } - - // 嵌套对象替换:item.string -> "nestedReplace" - RyanJsonReplaceByKey(RyanJsonGetObjectToKey(json, "item"), "string", RyanJsonCreateString("string", "nestedReplace")); - { - RyanJson_t v = RyanJsonGetObjectToKey(RyanJsonGetObjectToKey(json, "item"), "string"); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "nestedReplace") == 0, { goto err; }); - } - - // 混合数组替换:各类型位置 - // 0:int -> "intReplaced" - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "array"), 0, RyanJsonCreateString(NULL, "intReplaced")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "array"), 0); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "intReplaced") == 0, { goto err; }); - } - // 1:double -> "doubleReplaced" - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "array"), 1, RyanJsonCreateString(NULL, "doubleReplaced")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "array"), 1); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "doubleReplaced") == 0, { goto err; }); - } - // 2:string -> "stringReplaced" - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "array"), 2, RyanJsonCreateString(NULL, "stringReplaced")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "array"), 2); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "stringReplaced") == 0, { goto err; }); - } - // 3:bool -> "boolReplaced" - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "array"), 3, RyanJsonCreateString(NULL, "boolReplaced")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "array"), 3); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "boolReplaced") == 0, { goto err; }); - } - // 5:null -> "nullReplaced" - RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "array"), 5, RyanJsonCreateString(NULL, "nullReplaced")); - { - RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "array"), 5); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "nullReplaced") == 0, { goto err; }); - } - - // 对象替换测试:arrayString -> "arrayString2222" - RyanJsonReplaceByKey(json, "arrayString", RyanJsonCreateString("", "arrayString2222")); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "arrayString"); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "arrayString2222") == 0, { goto err; }); - } - - // 修改数组节点为对象节点:arrayDouble -> duplicate(item) - RyanJson_t duplicateJson = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); - RyanJsonReplaceByKey(json, "arrayDouble", duplicateJson); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "arrayDouble"); - RyanJsonCheckCode(RyanJsonIsObject(v), { goto err; }); - } - - // 替换普通 key 的值:string2222 -> "world" - RyanJsonReplaceByKey(json, "string2222", RyanJsonCreateString("string2222", "world")); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "string2222"); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "world") == 0, { goto err; }); - } - - // 替换布尔值:boolTrue -> false - RyanJsonReplaceByKey(json, "boolTrue", RyanJsonCreateBool("boolTrue", RyanJsonFalse)); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "boolTrue"); - RyanJsonCheckCode(RyanJsonIsBool(v), { goto err; }); - RyanJsonCheckCode(RyanJsonGetBoolValue(v) == RyanJsonFalse, { goto err; }); - } - - // 替换 null:null -> "notNull" - RyanJsonReplaceByKey(json, "null", RyanJsonCreateString("null", "notNull")); - { - RyanJson_t v = RyanJsonGetObjectToKey(json, "null"); - RyanJsonCheckCode(RyanJsonIsString(v), { goto err; }); - RyanJsonCheckCode(strcmp(RyanJsonGetStringValue(v), "notNull") == 0, { goto err; }); - } - - char *str = RyanJsonPrint(json, 1024, RyanJsonTrue, NULL); - RyanJsonFree(str); - RyanJsonDelete(json); - return RyanJsonTrue; - -err: - RyanJsonDelete(json); - return RyanJsonFalse; -} diff --git a/test/baseTest/RyanJsonBaseTestUtile.c b/test/baseTest/RyanJsonBaseTestUtile.c deleted file mode 100644 index e7c9bfc..0000000 --- a/test/baseTest/RyanJsonBaseTestUtile.c +++ /dev/null @@ -1,169 +0,0 @@ - -#include "RyanJsonBaseTest.h" - -/* --------------------------------------- jsonTest ------------------------------------------- */ -// !(fabs(RyanJsonGetDoubleValue(RyanJsonGetObjectToKey(json, "double")) - 16.89) < 1e-6) -RyanJsonBool_e compare_double(double a, double b) -{ - double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); - return (fabs(a - b) <= maxVal * DBL_EPSILON); -} - -void printJsonDebug(RyanJson_t json) -{ - char *str = RyanJsonPrint(json, 1024, RyanJsonTrue, NULL); - printf("aa %s\r\n", str); - RyanJsonFree(str); -} - -RyanJsonBool_e rootNodeCheckTest(RyanJson_t json) -{ - if (!RyanJsonIsInt(RyanJsonGetObjectToKey(json, "inter")) || 16 != RyanJsonGetIntValue(RyanJsonGetObjectToKey(json, "inter"))) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsDouble(RyanJsonGetObjectToKey(json, "double")) || - !compare_double(RyanJsonGetDoubleValue(RyanJsonGetObjectToKey(json, "double")), 16.89)) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsString(RyanJsonGetObjectToKey(json, "string")) || - strcmp(RyanJsonGetStringValue(RyanJsonGetObjectToKey(json, "string")), "hello")) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsBool(RyanJsonGetObjectToKey(json, "boolTrue")) || - RyanJsonGetBoolValue(RyanJsonGetObjectToKey(json, "boolTrue")) != RyanJsonTrue) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsBool(RyanJsonGetObjectToKey(json, "boolFalse")) || - RyanJsonGetBoolValue(RyanJsonGetObjectToKey(json, "boolFalse")) != RyanJsonFalse) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsNull(RyanJsonGetObjectToKey(json, "null"))) { RyanJsonCheckReturnFalse(NULL); } - - return RyanJsonTrue; -} - -RyanJsonBool_e itemNodeCheckTest(RyanJson_t json) -{ - RyanJson_t item = RyanJsonGetObjectToKey(json, "item"); - if (RyanJsonTrue != rootNodeCheckTest(item)) { RyanJsonCheckReturnFalse(NULL); } - - return RyanJsonTrue; -} - -RyanJsonBool_e arrayNodeCheckTest(RyanJson_t json) -{ - RyanJson_t item = NULL; - - // 判断是不是数组类型 - if (!RyanJsonIsArray(RyanJsonGetObjectToKey(json, "arrayInt"))) { RyanJsonCheckReturnFalse(NULL); } - - if (!RyanJsonIsArray(RyanJsonGetObjectToKey(json, "arrayDouble"))) { RyanJsonCheckReturnFalse(NULL); } - - if (!RyanJsonIsArray(RyanJsonGetObjectToKey(json, "arrayString"))) { RyanJsonCheckReturnFalse(NULL); } - - if (!RyanJsonIsArray(RyanJsonGetObjectToKey(json, "array"))) { RyanJsonCheckReturnFalse(NULL); } - - /** - * @brief 检查弱类型数组 - * - */ - // array: [16, 16.89, "hello", true, false, null], - if (!RyanJsonIsInt(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 0)) || - 16 != RyanJsonGetIntValue(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 0))) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsDouble(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 1)) || - !compare_double(RyanJsonGetDoubleValue(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 1)), 16.89)) - { - printf("%s:%d 解析失败 %f\r\n", __FILE__, __LINE__, - RyanJsonGetDoubleValue(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 1))); - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsString(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 2)) || - 0 != strcmp(RyanJsonGetStringValue(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 2)), "hello")) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsBool(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 3)) || - RyanJsonGetBoolValue(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 3)) != RyanJsonTrue) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsBool(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 4)) || - RyanJsonGetBoolValue(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 4)) != RyanJsonFalse) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (!RyanJsonIsNull(RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "array"), 5))) { RyanJsonCheckReturnFalse(NULL); } - - /** - * @brief 检查强类型数组 - * - */ - for (int32_t count = 0; count < RyanJsonGetSize(RyanJsonGetObjectToKey(json, "arrayInt")); count++) - { - item = RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "arrayInt"), count); - if (!RyanJsonIsInt(item) || 16 != RyanJsonGetIntValue(item)) { RyanJsonCheckReturnFalse(NULL); } - } - - for (int32_t count = 0; count < RyanJsonGetSize(RyanJsonGetObjectToKey(json, "arrayDouble")); count++) - { - item = RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "arrayDouble"), count); - if (!RyanJsonIsDouble(item) || fabs(RyanJsonGetDoubleValue(item) - 16.8) < 0.001) { RyanJsonCheckReturnFalse(NULL); } - } - - for (int32_t count = 0; count < RyanJsonGetSize(RyanJsonGetObjectToKey(json, "arrayString")); count++) - { - item = RyanJsonGetObjectByIndex(RyanJsonGetObjectToKey(json, "arrayString"), count); - if (!RyanJsonIsString(item) || strcmp(RyanJsonGetStringValue(item), "hello")) { RyanJsonCheckReturnFalse(NULL); } - } - - if (6 != RyanJsonGetSize(RyanJsonGetObjectToKey(json, "array"))) { RyanJsonCheckReturnFalse(NULL); } - - return RyanJsonTrue; -} - -RyanJsonBool_e arrayItemNodeCheckTest(RyanJson_t json) -{ - if (!RyanJsonIsArray(RyanJsonGetObjectToKey(json, "arrayItem"))) { RyanJsonCheckReturnFalse(NULL); } - - if (RyanJsonTrue != rootNodeCheckTest(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 0))) - { - RyanJsonCheckReturnFalse(NULL); - } - - if (RyanJsonTrue != rootNodeCheckTest(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 1))) - { - RyanJsonCheckReturnFalse(NULL); - } - return RyanJsonTrue; -} - -RyanJsonBool_e RyanJsonBaseTestCheckRoot(RyanJson_t pJson) -{ - RyanJsonCheckReturnFalse(RyanJsonTrue == rootNodeCheckTest(pJson)); - - RyanJsonCheckReturnFalse(RyanJsonTrue == itemNodeCheckTest(pJson)); - - RyanJsonCheckReturnFalse(RyanJsonTrue == arrayNodeCheckTest(pJson)); - - RyanJsonCheckReturnFalse(RyanJsonTrue == arrayItemNodeCheckTest(pJson)); - - return RyanJsonTrue; -} diff --git a/test/RFC8259JsonData/i_number_double_huge_neg_exp.json b/test/data/rfc8259/i_number_double_huge_neg_exp.json similarity index 100% rename from test/RFC8259JsonData/i_number_double_huge_neg_exp.json rename to test/data/rfc8259/i_number_double_huge_neg_exp.json diff --git a/test/RFC8259JsonData/i_number_huge_exp.json b/test/data/rfc8259/i_number_huge_exp.json similarity index 100% rename from test/RFC8259JsonData/i_number_huge_exp.json rename to test/data/rfc8259/i_number_huge_exp.json diff --git a/test/RFC8259JsonData/i_number_neg_int_huge_exp.json b/test/data/rfc8259/i_number_neg_int_huge_exp.json similarity index 100% rename from test/RFC8259JsonData/i_number_neg_int_huge_exp.json rename to test/data/rfc8259/i_number_neg_int_huge_exp.json diff --git a/test/RFC8259JsonData/i_number_pos_double_huge_exp.json b/test/data/rfc8259/i_number_pos_double_huge_exp.json similarity index 100% rename from test/RFC8259JsonData/i_number_pos_double_huge_exp.json rename to test/data/rfc8259/i_number_pos_double_huge_exp.json diff --git a/test/RFC8259JsonData/i_number_real_neg_overflow.json b/test/data/rfc8259/i_number_real_neg_overflow.json similarity index 100% rename from test/RFC8259JsonData/i_number_real_neg_overflow.json rename to test/data/rfc8259/i_number_real_neg_overflow.json diff --git a/test/RFC8259JsonData/i_number_real_pos_overflow.json b/test/data/rfc8259/i_number_real_pos_overflow.json similarity index 100% rename from test/RFC8259JsonData/i_number_real_pos_overflow.json rename to test/data/rfc8259/i_number_real_pos_overflow.json diff --git a/test/RFC8259JsonData/i_number_real_underflow.json b/test/data/rfc8259/i_number_real_underflow.json similarity index 100% rename from test/RFC8259JsonData/i_number_real_underflow.json rename to test/data/rfc8259/i_number_real_underflow.json diff --git a/test/RFC8259JsonData/i_number_too_big_neg_int.json b/test/data/rfc8259/i_number_too_big_neg_int.json similarity index 100% rename from test/RFC8259JsonData/i_number_too_big_neg_int.json rename to test/data/rfc8259/i_number_too_big_neg_int.json diff --git a/test/RFC8259JsonData/i_number_too_big_pos_int.json b/test/data/rfc8259/i_number_too_big_pos_int.json similarity index 100% rename from test/RFC8259JsonData/i_number_too_big_pos_int.json rename to test/data/rfc8259/i_number_too_big_pos_int.json diff --git a/test/RFC8259JsonData/i_number_very_big_negative_int.json b/test/data/rfc8259/i_number_very_big_negative_int.json similarity index 100% rename from test/RFC8259JsonData/i_number_very_big_negative_int.json rename to test/data/rfc8259/i_number_very_big_negative_int.json diff --git a/test/RFC8259JsonData/i_object_key_lone_2nd_surrogate.json b/test/data/rfc8259/i_object_key_lone_2nd_surrogate.json similarity index 100% rename from test/RFC8259JsonData/i_object_key_lone_2nd_surrogate.json rename to test/data/rfc8259/i_object_key_lone_2nd_surrogate.json diff --git a/test/RFC8259JsonData/i_string_1st_surrogate_but_2nd_missing.json b/test/data/rfc8259/i_string_1st_surrogate_but_2nd_missing.json similarity index 100% rename from test/RFC8259JsonData/i_string_1st_surrogate_but_2nd_missing.json rename to test/data/rfc8259/i_string_1st_surrogate_but_2nd_missing.json diff --git a/test/RFC8259JsonData/i_string_1st_valid_surrogate_2nd_invalid.json b/test/data/rfc8259/i_string_1st_valid_surrogate_2nd_invalid.json similarity index 100% rename from test/RFC8259JsonData/i_string_1st_valid_surrogate_2nd_invalid.json rename to test/data/rfc8259/i_string_1st_valid_surrogate_2nd_invalid.json diff --git a/test/RFC8259JsonData/i_string_UTF-16LE_with_BOM.json b/test/data/rfc8259/i_string_UTF-16LE_with_BOM.json similarity index 100% rename from test/RFC8259JsonData/i_string_UTF-16LE_with_BOM.json rename to test/data/rfc8259/i_string_UTF-16LE_with_BOM.json diff --git a/test/RFC8259JsonData/i_string_UTF-8_invalid_sequence.json b/test/data/rfc8259/i_string_UTF-8_invalid_sequence.json similarity index 100% rename from test/RFC8259JsonData/i_string_UTF-8_invalid_sequence.json rename to test/data/rfc8259/i_string_UTF-8_invalid_sequence.json diff --git a/test/RFC8259JsonData/i_string_UTF8_surrogate_U+D800.json b/test/data/rfc8259/i_string_UTF8_surrogate_U+D800.json similarity index 100% rename from test/RFC8259JsonData/i_string_UTF8_surrogate_U+D800.json rename to test/data/rfc8259/i_string_UTF8_surrogate_U+D800.json diff --git a/test/RFC8259JsonData/i_string_incomplete_surrogate_and_escape_valid.json b/test/data/rfc8259/i_string_incomplete_surrogate_and_escape_valid.json similarity index 100% rename from test/RFC8259JsonData/i_string_incomplete_surrogate_and_escape_valid.json rename to test/data/rfc8259/i_string_incomplete_surrogate_and_escape_valid.json diff --git a/test/RFC8259JsonData/i_string_incomplete_surrogate_pair.json b/test/data/rfc8259/i_string_incomplete_surrogate_pair.json similarity index 100% rename from test/RFC8259JsonData/i_string_incomplete_surrogate_pair.json rename to test/data/rfc8259/i_string_incomplete_surrogate_pair.json diff --git a/test/RFC8259JsonData/i_string_incomplete_surrogates_escape_valid.json b/test/data/rfc8259/i_string_incomplete_surrogates_escape_valid.json similarity index 100% rename from test/RFC8259JsonData/i_string_incomplete_surrogates_escape_valid.json rename to test/data/rfc8259/i_string_incomplete_surrogates_escape_valid.json diff --git a/test/RFC8259JsonData/i_string_invalid_lonely_surrogate.json b/test/data/rfc8259/i_string_invalid_lonely_surrogate.json similarity index 100% rename from test/RFC8259JsonData/i_string_invalid_lonely_surrogate.json rename to test/data/rfc8259/i_string_invalid_lonely_surrogate.json diff --git a/test/RFC8259JsonData/i_string_invalid_surrogate.json b/test/data/rfc8259/i_string_invalid_surrogate.json similarity index 100% rename from test/RFC8259JsonData/i_string_invalid_surrogate.json rename to test/data/rfc8259/i_string_invalid_surrogate.json diff --git a/test/RFC8259JsonData/i_string_invalid_utf-8.json b/test/data/rfc8259/i_string_invalid_utf-8.json similarity index 100% rename from test/RFC8259JsonData/i_string_invalid_utf-8.json rename to test/data/rfc8259/i_string_invalid_utf-8.json diff --git a/test/RFC8259JsonData/i_string_inverted_surrogates_U+1D11E.json b/test/data/rfc8259/i_string_inverted_surrogates_U+1D11E.json similarity index 100% rename from test/RFC8259JsonData/i_string_inverted_surrogates_U+1D11E.json rename to test/data/rfc8259/i_string_inverted_surrogates_U+1D11E.json diff --git a/test/RFC8259JsonData/i_string_iso_latin_1.json b/test/data/rfc8259/i_string_iso_latin_1.json similarity index 100% rename from test/RFC8259JsonData/i_string_iso_latin_1.json rename to test/data/rfc8259/i_string_iso_latin_1.json diff --git a/test/RFC8259JsonData/i_string_lone_second_surrogate.json b/test/data/rfc8259/i_string_lone_second_surrogate.json similarity index 100% rename from test/RFC8259JsonData/i_string_lone_second_surrogate.json rename to test/data/rfc8259/i_string_lone_second_surrogate.json diff --git a/test/RFC8259JsonData/i_string_lone_utf8_continuation_byte.json b/test/data/rfc8259/i_string_lone_utf8_continuation_byte.json similarity index 100% rename from test/RFC8259JsonData/i_string_lone_utf8_continuation_byte.json rename to test/data/rfc8259/i_string_lone_utf8_continuation_byte.json diff --git a/test/RFC8259JsonData/i_string_not_in_unicode_range.json b/test/data/rfc8259/i_string_not_in_unicode_range.json similarity index 100% rename from test/RFC8259JsonData/i_string_not_in_unicode_range.json rename to test/data/rfc8259/i_string_not_in_unicode_range.json diff --git a/test/RFC8259JsonData/i_string_overlong_sequence_2_bytes.json b/test/data/rfc8259/i_string_overlong_sequence_2_bytes.json similarity index 100% rename from test/RFC8259JsonData/i_string_overlong_sequence_2_bytes.json rename to test/data/rfc8259/i_string_overlong_sequence_2_bytes.json diff --git a/test/RFC8259JsonData/i_string_overlong_sequence_6_bytes.json b/test/data/rfc8259/i_string_overlong_sequence_6_bytes.json similarity index 100% rename from test/RFC8259JsonData/i_string_overlong_sequence_6_bytes.json rename to test/data/rfc8259/i_string_overlong_sequence_6_bytes.json diff --git a/test/RFC8259JsonData/i_string_overlong_sequence_6_bytes_null.json b/test/data/rfc8259/i_string_overlong_sequence_6_bytes_null.json similarity index 100% rename from test/RFC8259JsonData/i_string_overlong_sequence_6_bytes_null.json rename to test/data/rfc8259/i_string_overlong_sequence_6_bytes_null.json diff --git a/test/RFC8259JsonData/i_string_truncated-utf-8.json b/test/data/rfc8259/i_string_truncated-utf-8.json similarity index 100% rename from test/RFC8259JsonData/i_string_truncated-utf-8.json rename to test/data/rfc8259/i_string_truncated-utf-8.json diff --git a/test/RFC8259JsonData/i_string_utf16BE_no_BOM.json b/test/data/rfc8259/i_string_utf16BE_no_BOM.json similarity index 100% rename from test/RFC8259JsonData/i_string_utf16BE_no_BOM.json rename to test/data/rfc8259/i_string_utf16BE_no_BOM.json diff --git a/test/RFC8259JsonData/i_string_utf16LE_no_BOM.json b/test/data/rfc8259/i_string_utf16LE_no_BOM.json similarity index 100% rename from test/RFC8259JsonData/i_string_utf16LE_no_BOM.json rename to test/data/rfc8259/i_string_utf16LE_no_BOM.json diff --git a/test/RFC8259JsonData/i_structure_500_nested_arrays.json b/test/data/rfc8259/i_structure_500_nested_arrays.json similarity index 100% rename from test/RFC8259JsonData/i_structure_500_nested_arrays.json rename to test/data/rfc8259/i_structure_500_nested_arrays.json diff --git a/test/RFC8259JsonData/i_structure_UTF-8_BOM_empty_object.json b/test/data/rfc8259/i_structure_UTF-8_BOM_empty_object.json similarity index 100% rename from test/RFC8259JsonData/i_structure_UTF-8_BOM_empty_object.json rename to test/data/rfc8259/i_structure_UTF-8_BOM_empty_object.json diff --git a/test/RFC8259JsonData/n_array_1_true_without_comma.json b/test/data/rfc8259/n_array_1_true_without_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_1_true_without_comma.json rename to test/data/rfc8259/n_array_1_true_without_comma.json diff --git a/test/RFC8259JsonData/n_array_a_invalid_utf8.json b/test/data/rfc8259/n_array_a_invalid_utf8.json similarity index 100% rename from test/RFC8259JsonData/n_array_a_invalid_utf8.json rename to test/data/rfc8259/n_array_a_invalid_utf8.json diff --git a/test/RFC8259JsonData/n_array_colon_instead_of_comma.json b/test/data/rfc8259/n_array_colon_instead_of_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_colon_instead_of_comma.json rename to test/data/rfc8259/n_array_colon_instead_of_comma.json diff --git a/test/RFC8259JsonData/n_array_comma_after_close.json b/test/data/rfc8259/n_array_comma_after_close.json similarity index 100% rename from test/RFC8259JsonData/n_array_comma_after_close.json rename to test/data/rfc8259/n_array_comma_after_close.json diff --git a/test/RFC8259JsonData/n_array_comma_and_number.json b/test/data/rfc8259/n_array_comma_and_number.json similarity index 100% rename from test/RFC8259JsonData/n_array_comma_and_number.json rename to test/data/rfc8259/n_array_comma_and_number.json diff --git a/test/RFC8259JsonData/n_array_double_comma.json b/test/data/rfc8259/n_array_double_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_double_comma.json rename to test/data/rfc8259/n_array_double_comma.json diff --git a/test/RFC8259JsonData/n_array_double_extra_comma.json b/test/data/rfc8259/n_array_double_extra_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_double_extra_comma.json rename to test/data/rfc8259/n_array_double_extra_comma.json diff --git a/test/RFC8259JsonData/n_array_extra_close.json b/test/data/rfc8259/n_array_extra_close.json similarity index 100% rename from test/RFC8259JsonData/n_array_extra_close.json rename to test/data/rfc8259/n_array_extra_close.json diff --git a/test/RFC8259JsonData/n_array_extra_comma.json b/test/data/rfc8259/n_array_extra_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_extra_comma.json rename to test/data/rfc8259/n_array_extra_comma.json diff --git a/test/RFC8259JsonData/n_array_incomplete.json b/test/data/rfc8259/n_array_incomplete.json similarity index 100% rename from test/RFC8259JsonData/n_array_incomplete.json rename to test/data/rfc8259/n_array_incomplete.json diff --git a/test/RFC8259JsonData/n_array_incomplete_invalid_value.json b/test/data/rfc8259/n_array_incomplete_invalid_value.json similarity index 100% rename from test/RFC8259JsonData/n_array_incomplete_invalid_value.json rename to test/data/rfc8259/n_array_incomplete_invalid_value.json diff --git a/test/RFC8259JsonData/n_array_inner_array_no_comma.json b/test/data/rfc8259/n_array_inner_array_no_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_inner_array_no_comma.json rename to test/data/rfc8259/n_array_inner_array_no_comma.json diff --git a/test/RFC8259JsonData/n_array_invalid_utf8.json b/test/data/rfc8259/n_array_invalid_utf8.json similarity index 100% rename from test/RFC8259JsonData/n_array_invalid_utf8.json rename to test/data/rfc8259/n_array_invalid_utf8.json diff --git a/test/RFC8259JsonData/n_array_items_separated_by_semicolon.json b/test/data/rfc8259/n_array_items_separated_by_semicolon.json similarity index 100% rename from test/RFC8259JsonData/n_array_items_separated_by_semicolon.json rename to test/data/rfc8259/n_array_items_separated_by_semicolon.json diff --git a/test/RFC8259JsonData/n_array_just_comma.json b/test/data/rfc8259/n_array_just_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_just_comma.json rename to test/data/rfc8259/n_array_just_comma.json diff --git a/test/RFC8259JsonData/n_array_just_minus.json b/test/data/rfc8259/n_array_just_minus.json similarity index 100% rename from test/RFC8259JsonData/n_array_just_minus.json rename to test/data/rfc8259/n_array_just_minus.json diff --git a/test/RFC8259JsonData/n_array_missing_value.json b/test/data/rfc8259/n_array_missing_value.json similarity index 100% rename from test/RFC8259JsonData/n_array_missing_value.json rename to test/data/rfc8259/n_array_missing_value.json diff --git a/test/RFC8259JsonData/n_array_newlines_unclosed.json b/test/data/rfc8259/n_array_newlines_unclosed.json similarity index 100% rename from test/RFC8259JsonData/n_array_newlines_unclosed.json rename to test/data/rfc8259/n_array_newlines_unclosed.json diff --git a/test/RFC8259JsonData/n_array_number_and_comma.json b/test/data/rfc8259/n_array_number_and_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_number_and_comma.json rename to test/data/rfc8259/n_array_number_and_comma.json diff --git a/test/RFC8259JsonData/n_array_number_and_several_commas.json b/test/data/rfc8259/n_array_number_and_several_commas.json similarity index 100% rename from test/RFC8259JsonData/n_array_number_and_several_commas.json rename to test/data/rfc8259/n_array_number_and_several_commas.json diff --git a/test/RFC8259JsonData/n_array_spaces_vertical_tab_formfeed.json b/test/data/rfc8259/n_array_spaces_vertical_tab_formfeed.json similarity index 100% rename from test/RFC8259JsonData/n_array_spaces_vertical_tab_formfeed.json rename to test/data/rfc8259/n_array_spaces_vertical_tab_formfeed.json diff --git a/test/RFC8259JsonData/n_array_star_inside.json b/test/data/rfc8259/n_array_star_inside.json similarity index 100% rename from test/RFC8259JsonData/n_array_star_inside.json rename to test/data/rfc8259/n_array_star_inside.json diff --git a/test/RFC8259JsonData/n_array_unclosed.json b/test/data/rfc8259/n_array_unclosed.json similarity index 100% rename from test/RFC8259JsonData/n_array_unclosed.json rename to test/data/rfc8259/n_array_unclosed.json diff --git a/test/RFC8259JsonData/n_array_unclosed_trailing_comma.json b/test/data/rfc8259/n_array_unclosed_trailing_comma.json similarity index 100% rename from test/RFC8259JsonData/n_array_unclosed_trailing_comma.json rename to test/data/rfc8259/n_array_unclosed_trailing_comma.json diff --git a/test/RFC8259JsonData/n_array_unclosed_with_new_lines.json b/test/data/rfc8259/n_array_unclosed_with_new_lines.json similarity index 100% rename from test/RFC8259JsonData/n_array_unclosed_with_new_lines.json rename to test/data/rfc8259/n_array_unclosed_with_new_lines.json diff --git a/test/RFC8259JsonData/n_array_unclosed_with_object_inside.json b/test/data/rfc8259/n_array_unclosed_with_object_inside.json similarity index 100% rename from test/RFC8259JsonData/n_array_unclosed_with_object_inside.json rename to test/data/rfc8259/n_array_unclosed_with_object_inside.json diff --git a/test/RFC8259JsonData/n_incomplete_false.json b/test/data/rfc8259/n_incomplete_false.json similarity index 100% rename from test/RFC8259JsonData/n_incomplete_false.json rename to test/data/rfc8259/n_incomplete_false.json diff --git a/test/RFC8259JsonData/n_incomplete_null.json b/test/data/rfc8259/n_incomplete_null.json similarity index 100% rename from test/RFC8259JsonData/n_incomplete_null.json rename to test/data/rfc8259/n_incomplete_null.json diff --git a/test/RFC8259JsonData/n_incomplete_true.json b/test/data/rfc8259/n_incomplete_true.json similarity index 100% rename from test/RFC8259JsonData/n_incomplete_true.json rename to test/data/rfc8259/n_incomplete_true.json diff --git a/test/RFC8259JsonData/n_multidigit_number_then_00.json b/test/data/rfc8259/n_multidigit_number_then_00.json similarity index 100% rename from test/RFC8259JsonData/n_multidigit_number_then_00.json rename to test/data/rfc8259/n_multidigit_number_then_00.json diff --git a/test/RFC8259JsonData/n_number_++.json b/test/data/rfc8259/n_number_++.json similarity index 100% rename from test/RFC8259JsonData/n_number_++.json rename to test/data/rfc8259/n_number_++.json diff --git a/test/RFC8259JsonData/n_number_+1.json b/test/data/rfc8259/n_number_+1.json similarity index 100% rename from test/RFC8259JsonData/n_number_+1.json rename to test/data/rfc8259/n_number_+1.json diff --git a/test/RFC8259JsonData/n_number_+Inf.json b/test/data/rfc8259/n_number_+Inf.json similarity index 100% rename from test/RFC8259JsonData/n_number_+Inf.json rename to test/data/rfc8259/n_number_+Inf.json diff --git a/test/RFC8259JsonData/n_number_-01.json b/test/data/rfc8259/n_number_-01.json similarity index 100% rename from test/RFC8259JsonData/n_number_-01.json rename to test/data/rfc8259/n_number_-01.json diff --git a/test/RFC8259JsonData/n_number_-1.0..json b/test/data/rfc8259/n_number_-1.0..json similarity index 100% rename from test/RFC8259JsonData/n_number_-1.0..json rename to test/data/rfc8259/n_number_-1.0..json diff --git a/test/RFC8259JsonData/n_number_-2..json b/test/data/rfc8259/n_number_-2..json similarity index 100% rename from test/RFC8259JsonData/n_number_-2..json rename to test/data/rfc8259/n_number_-2..json diff --git a/test/RFC8259JsonData/n_number_-NaN.json b/test/data/rfc8259/n_number_-NaN.json similarity index 100% rename from test/RFC8259JsonData/n_number_-NaN.json rename to test/data/rfc8259/n_number_-NaN.json diff --git a/test/RFC8259JsonData/n_number_.-1.json b/test/data/rfc8259/n_number_.-1.json similarity index 100% rename from test/RFC8259JsonData/n_number_.-1.json rename to test/data/rfc8259/n_number_.-1.json diff --git a/test/RFC8259JsonData/n_number_.2e-3.json b/test/data/rfc8259/n_number_.2e-3.json similarity index 100% rename from test/RFC8259JsonData/n_number_.2e-3.json rename to test/data/rfc8259/n_number_.2e-3.json diff --git a/test/RFC8259JsonData/n_number_0.1.2.json b/test/data/rfc8259/n_number_0.1.2.json similarity index 100% rename from test/RFC8259JsonData/n_number_0.1.2.json rename to test/data/rfc8259/n_number_0.1.2.json diff --git a/test/RFC8259JsonData/n_number_0.3e+.json b/test/data/rfc8259/n_number_0.3e+.json similarity index 100% rename from test/RFC8259JsonData/n_number_0.3e+.json rename to test/data/rfc8259/n_number_0.3e+.json diff --git a/test/RFC8259JsonData/n_number_0.3e.json b/test/data/rfc8259/n_number_0.3e.json similarity index 100% rename from test/RFC8259JsonData/n_number_0.3e.json rename to test/data/rfc8259/n_number_0.3e.json diff --git a/test/RFC8259JsonData/n_number_0.e1.json b/test/data/rfc8259/n_number_0.e1.json similarity index 100% rename from test/RFC8259JsonData/n_number_0.e1.json rename to test/data/rfc8259/n_number_0.e1.json diff --git a/test/RFC8259JsonData/n_number_0_capital_E+.json b/test/data/rfc8259/n_number_0_capital_E+.json similarity index 100% rename from test/RFC8259JsonData/n_number_0_capital_E+.json rename to test/data/rfc8259/n_number_0_capital_E+.json diff --git a/test/RFC8259JsonData/n_number_0_capital_E.json b/test/data/rfc8259/n_number_0_capital_E.json similarity index 100% rename from test/RFC8259JsonData/n_number_0_capital_E.json rename to test/data/rfc8259/n_number_0_capital_E.json diff --git a/test/RFC8259JsonData/n_number_0e+.json b/test/data/rfc8259/n_number_0e+.json similarity index 100% rename from test/RFC8259JsonData/n_number_0e+.json rename to test/data/rfc8259/n_number_0e+.json diff --git a/test/RFC8259JsonData/n_number_0e.json b/test/data/rfc8259/n_number_0e.json similarity index 100% rename from test/RFC8259JsonData/n_number_0e.json rename to test/data/rfc8259/n_number_0e.json diff --git a/test/RFC8259JsonData/n_number_1.0e+.json b/test/data/rfc8259/n_number_1.0e+.json similarity index 100% rename from test/RFC8259JsonData/n_number_1.0e+.json rename to test/data/rfc8259/n_number_1.0e+.json diff --git a/test/RFC8259JsonData/n_number_1.0e-.json b/test/data/rfc8259/n_number_1.0e-.json similarity index 100% rename from test/RFC8259JsonData/n_number_1.0e-.json rename to test/data/rfc8259/n_number_1.0e-.json diff --git a/test/RFC8259JsonData/n_number_1.0e.json b/test/data/rfc8259/n_number_1.0e.json similarity index 100% rename from test/RFC8259JsonData/n_number_1.0e.json rename to test/data/rfc8259/n_number_1.0e.json diff --git a/test/RFC8259JsonData/n_number_1_000.json b/test/data/rfc8259/n_number_1_000.json similarity index 100% rename from test/RFC8259JsonData/n_number_1_000.json rename to test/data/rfc8259/n_number_1_000.json diff --git a/test/RFC8259JsonData/n_number_1eE2.json b/test/data/rfc8259/n_number_1eE2.json similarity index 100% rename from test/RFC8259JsonData/n_number_1eE2.json rename to test/data/rfc8259/n_number_1eE2.json diff --git a/test/RFC8259JsonData/n_number_2.e+3.json b/test/data/rfc8259/n_number_2.e+3.json similarity index 100% rename from test/RFC8259JsonData/n_number_2.e+3.json rename to test/data/rfc8259/n_number_2.e+3.json diff --git a/test/RFC8259JsonData/n_number_2.e-3.json b/test/data/rfc8259/n_number_2.e-3.json similarity index 100% rename from test/RFC8259JsonData/n_number_2.e-3.json rename to test/data/rfc8259/n_number_2.e-3.json diff --git a/test/RFC8259JsonData/n_number_2.e3.json b/test/data/rfc8259/n_number_2.e3.json similarity index 100% rename from test/RFC8259JsonData/n_number_2.e3.json rename to test/data/rfc8259/n_number_2.e3.json diff --git a/test/RFC8259JsonData/n_number_9.e+.json b/test/data/rfc8259/n_number_9.e+.json similarity index 100% rename from test/RFC8259JsonData/n_number_9.e+.json rename to test/data/rfc8259/n_number_9.e+.json diff --git a/test/RFC8259JsonData/n_number_Inf.json b/test/data/rfc8259/n_number_Inf.json similarity index 100% rename from test/RFC8259JsonData/n_number_Inf.json rename to test/data/rfc8259/n_number_Inf.json diff --git a/test/RFC8259JsonData/n_number_NaN.json b/test/data/rfc8259/n_number_NaN.json similarity index 100% rename from test/RFC8259JsonData/n_number_NaN.json rename to test/data/rfc8259/n_number_NaN.json diff --git a/test/RFC8259JsonData/n_number_U+FF11_fullwidth_digit_one.json b/test/data/rfc8259/n_number_U+FF11_fullwidth_digit_one.json similarity index 100% rename from test/RFC8259JsonData/n_number_U+FF11_fullwidth_digit_one.json rename to test/data/rfc8259/n_number_U+FF11_fullwidth_digit_one.json diff --git a/test/RFC8259JsonData/n_number_expression.json b/test/data/rfc8259/n_number_expression.json similarity index 100% rename from test/RFC8259JsonData/n_number_expression.json rename to test/data/rfc8259/n_number_expression.json diff --git a/test/RFC8259JsonData/n_number_hex_1_digit.json b/test/data/rfc8259/n_number_hex_1_digit.json similarity index 100% rename from test/RFC8259JsonData/n_number_hex_1_digit.json rename to test/data/rfc8259/n_number_hex_1_digit.json diff --git a/test/RFC8259JsonData/n_number_hex_2_digits.json b/test/data/rfc8259/n_number_hex_2_digits.json similarity index 100% rename from test/RFC8259JsonData/n_number_hex_2_digits.json rename to test/data/rfc8259/n_number_hex_2_digits.json diff --git a/test/RFC8259JsonData/n_number_infinity.json b/test/data/rfc8259/n_number_infinity.json similarity index 100% rename from test/RFC8259JsonData/n_number_infinity.json rename to test/data/rfc8259/n_number_infinity.json diff --git a/test/RFC8259JsonData/n_number_invalid+-.json b/test/data/rfc8259/n_number_invalid+-.json similarity index 100% rename from test/RFC8259JsonData/n_number_invalid+-.json rename to test/data/rfc8259/n_number_invalid+-.json diff --git a/test/RFC8259JsonData/n_number_invalid-negative-real.json b/test/data/rfc8259/n_number_invalid-negative-real.json similarity index 100% rename from test/RFC8259JsonData/n_number_invalid-negative-real.json rename to test/data/rfc8259/n_number_invalid-negative-real.json diff --git a/test/RFC8259JsonData/n_number_invalid-utf-8-in-bigger-int.json b/test/data/rfc8259/n_number_invalid-utf-8-in-bigger-int.json similarity index 100% rename from test/RFC8259JsonData/n_number_invalid-utf-8-in-bigger-int.json rename to test/data/rfc8259/n_number_invalid-utf-8-in-bigger-int.json diff --git a/test/RFC8259JsonData/n_number_invalid-utf-8-in-exponent.json b/test/data/rfc8259/n_number_invalid-utf-8-in-exponent.json similarity index 100% rename from test/RFC8259JsonData/n_number_invalid-utf-8-in-exponent.json rename to test/data/rfc8259/n_number_invalid-utf-8-in-exponent.json diff --git a/test/RFC8259JsonData/n_number_invalid-utf-8-in-int.json b/test/data/rfc8259/n_number_invalid-utf-8-in-int.json similarity index 100% rename from test/RFC8259JsonData/n_number_invalid-utf-8-in-int.json rename to test/data/rfc8259/n_number_invalid-utf-8-in-int.json diff --git a/test/RFC8259JsonData/n_number_minus_infinity.json b/test/data/rfc8259/n_number_minus_infinity.json similarity index 100% rename from test/RFC8259JsonData/n_number_minus_infinity.json rename to test/data/rfc8259/n_number_minus_infinity.json diff --git a/test/RFC8259JsonData/n_number_minus_sign_with_trailing_garbage.json b/test/data/rfc8259/n_number_minus_sign_with_trailing_garbage.json similarity index 100% rename from test/RFC8259JsonData/n_number_minus_sign_with_trailing_garbage.json rename to test/data/rfc8259/n_number_minus_sign_with_trailing_garbage.json diff --git a/test/RFC8259JsonData/n_number_minus_space_1.json b/test/data/rfc8259/n_number_minus_space_1.json similarity index 100% rename from test/RFC8259JsonData/n_number_minus_space_1.json rename to test/data/rfc8259/n_number_minus_space_1.json diff --git a/test/RFC8259JsonData/n_number_neg_int_starting_with_zero.json b/test/data/rfc8259/n_number_neg_int_starting_with_zero.json similarity index 100% rename from test/RFC8259JsonData/n_number_neg_int_starting_with_zero.json rename to test/data/rfc8259/n_number_neg_int_starting_with_zero.json diff --git a/test/RFC8259JsonData/n_number_neg_real_without_int_part.json b/test/data/rfc8259/n_number_neg_real_without_int_part.json similarity index 100% rename from test/RFC8259JsonData/n_number_neg_real_without_int_part.json rename to test/data/rfc8259/n_number_neg_real_without_int_part.json diff --git a/test/RFC8259JsonData/n_number_neg_with_garbage_at_end.json b/test/data/rfc8259/n_number_neg_with_garbage_at_end.json similarity index 100% rename from test/RFC8259JsonData/n_number_neg_with_garbage_at_end.json rename to test/data/rfc8259/n_number_neg_with_garbage_at_end.json diff --git a/test/RFC8259JsonData/n_number_real_garbage_after_e.json b/test/data/rfc8259/n_number_real_garbage_after_e.json similarity index 100% rename from test/RFC8259JsonData/n_number_real_garbage_after_e.json rename to test/data/rfc8259/n_number_real_garbage_after_e.json diff --git a/test/RFC8259JsonData/n_number_real_with_invalid_utf8_after_e.json b/test/data/rfc8259/n_number_real_with_invalid_utf8_after_e.json similarity index 100% rename from test/RFC8259JsonData/n_number_real_with_invalid_utf8_after_e.json rename to test/data/rfc8259/n_number_real_with_invalid_utf8_after_e.json diff --git a/test/RFC8259JsonData/n_number_real_without_fractional_part.json b/test/data/rfc8259/n_number_real_without_fractional_part.json similarity index 100% rename from test/RFC8259JsonData/n_number_real_without_fractional_part.json rename to test/data/rfc8259/n_number_real_without_fractional_part.json diff --git a/test/RFC8259JsonData/n_number_starting_with_dot.json b/test/data/rfc8259/n_number_starting_with_dot.json similarity index 100% rename from test/RFC8259JsonData/n_number_starting_with_dot.json rename to test/data/rfc8259/n_number_starting_with_dot.json diff --git a/test/RFC8259JsonData/n_number_with_alpha.json b/test/data/rfc8259/n_number_with_alpha.json similarity index 100% rename from test/RFC8259JsonData/n_number_with_alpha.json rename to test/data/rfc8259/n_number_with_alpha.json diff --git a/test/RFC8259JsonData/n_number_with_alpha_char.json b/test/data/rfc8259/n_number_with_alpha_char.json similarity index 100% rename from test/RFC8259JsonData/n_number_with_alpha_char.json rename to test/data/rfc8259/n_number_with_alpha_char.json diff --git a/test/RFC8259JsonData/n_number_with_leading_zero.json b/test/data/rfc8259/n_number_with_leading_zero.json similarity index 100% rename from test/RFC8259JsonData/n_number_with_leading_zero.json rename to test/data/rfc8259/n_number_with_leading_zero.json diff --git a/test/RFC8259JsonData/n_object_bad_value.json b/test/data/rfc8259/n_object_bad_value.json similarity index 100% rename from test/RFC8259JsonData/n_object_bad_value.json rename to test/data/rfc8259/n_object_bad_value.json diff --git a/test/RFC8259JsonData/n_object_bracket_key.json b/test/data/rfc8259/n_object_bracket_key.json similarity index 100% rename from test/RFC8259JsonData/n_object_bracket_key.json rename to test/data/rfc8259/n_object_bracket_key.json diff --git a/test/RFC8259JsonData/n_object_comma_instead_of_colon.json b/test/data/rfc8259/n_object_comma_instead_of_colon.json similarity index 100% rename from test/RFC8259JsonData/n_object_comma_instead_of_colon.json rename to test/data/rfc8259/n_object_comma_instead_of_colon.json diff --git a/test/RFC8259JsonData/n_object_double_colon.json b/test/data/rfc8259/n_object_double_colon.json similarity index 100% rename from test/RFC8259JsonData/n_object_double_colon.json rename to test/data/rfc8259/n_object_double_colon.json diff --git a/test/RFC8259JsonData/n_object_emoji.json b/test/data/rfc8259/n_object_emoji.json similarity index 100% rename from test/RFC8259JsonData/n_object_emoji.json rename to test/data/rfc8259/n_object_emoji.json diff --git a/test/RFC8259JsonData/n_object_garbage_at_end.json b/test/data/rfc8259/n_object_garbage_at_end.json similarity index 100% rename from test/RFC8259JsonData/n_object_garbage_at_end.json rename to test/data/rfc8259/n_object_garbage_at_end.json diff --git a/test/RFC8259JsonData/n_object_key_with_single_quotes.json b/test/data/rfc8259/n_object_key_with_single_quotes.json similarity index 100% rename from test/RFC8259JsonData/n_object_key_with_single_quotes.json rename to test/data/rfc8259/n_object_key_with_single_quotes.json diff --git a/test/RFC8259JsonData/n_object_lone_continuation_byte_in_key_and_trailing_comma.json b/test/data/rfc8259/n_object_lone_continuation_byte_in_key_and_trailing_comma.json similarity index 100% rename from test/RFC8259JsonData/n_object_lone_continuation_byte_in_key_and_trailing_comma.json rename to test/data/rfc8259/n_object_lone_continuation_byte_in_key_and_trailing_comma.json diff --git a/test/RFC8259JsonData/n_object_missing_colon.json b/test/data/rfc8259/n_object_missing_colon.json similarity index 100% rename from test/RFC8259JsonData/n_object_missing_colon.json rename to test/data/rfc8259/n_object_missing_colon.json diff --git a/test/RFC8259JsonData/n_object_missing_key.json b/test/data/rfc8259/n_object_missing_key.json similarity index 100% rename from test/RFC8259JsonData/n_object_missing_key.json rename to test/data/rfc8259/n_object_missing_key.json diff --git a/test/RFC8259JsonData/n_object_missing_semicolon.json b/test/data/rfc8259/n_object_missing_semicolon.json similarity index 100% rename from test/RFC8259JsonData/n_object_missing_semicolon.json rename to test/data/rfc8259/n_object_missing_semicolon.json diff --git a/test/RFC8259JsonData/n_object_missing_value.json b/test/data/rfc8259/n_object_missing_value.json similarity index 100% rename from test/RFC8259JsonData/n_object_missing_value.json rename to test/data/rfc8259/n_object_missing_value.json diff --git a/test/RFC8259JsonData/n_object_no-colon.json b/test/data/rfc8259/n_object_no-colon.json similarity index 100% rename from test/RFC8259JsonData/n_object_no-colon.json rename to test/data/rfc8259/n_object_no-colon.json diff --git a/test/RFC8259JsonData/n_object_non_string_key.json b/test/data/rfc8259/n_object_non_string_key.json similarity index 100% rename from test/RFC8259JsonData/n_object_non_string_key.json rename to test/data/rfc8259/n_object_non_string_key.json diff --git a/test/RFC8259JsonData/n_object_non_string_key_but_huge_number_instead.json b/test/data/rfc8259/n_object_non_string_key_but_huge_number_instead.json similarity index 100% rename from test/RFC8259JsonData/n_object_non_string_key_but_huge_number_instead.json rename to test/data/rfc8259/n_object_non_string_key_but_huge_number_instead.json diff --git a/test/RFC8259JsonData/n_object_repeated_null_null.json b/test/data/rfc8259/n_object_repeated_null_null.json similarity index 100% rename from test/RFC8259JsonData/n_object_repeated_null_null.json rename to test/data/rfc8259/n_object_repeated_null_null.json diff --git a/test/RFC8259JsonData/n_object_several_trailing_commas.json b/test/data/rfc8259/n_object_several_trailing_commas.json similarity index 100% rename from test/RFC8259JsonData/n_object_several_trailing_commas.json rename to test/data/rfc8259/n_object_several_trailing_commas.json diff --git a/test/RFC8259JsonData/n_object_single_quote.json b/test/data/rfc8259/n_object_single_quote.json similarity index 100% rename from test/RFC8259JsonData/n_object_single_quote.json rename to test/data/rfc8259/n_object_single_quote.json diff --git a/test/RFC8259JsonData/n_object_trailing_comma.json b/test/data/rfc8259/n_object_trailing_comma.json similarity index 100% rename from test/RFC8259JsonData/n_object_trailing_comma.json rename to test/data/rfc8259/n_object_trailing_comma.json diff --git a/test/RFC8259JsonData/n_object_trailing_comment.json b/test/data/rfc8259/n_object_trailing_comment.json similarity index 100% rename from test/RFC8259JsonData/n_object_trailing_comment.json rename to test/data/rfc8259/n_object_trailing_comment.json diff --git a/test/RFC8259JsonData/n_object_trailing_comment_open.json b/test/data/rfc8259/n_object_trailing_comment_open.json similarity index 100% rename from test/RFC8259JsonData/n_object_trailing_comment_open.json rename to test/data/rfc8259/n_object_trailing_comment_open.json diff --git a/test/RFC8259JsonData/n_object_trailing_comment_slash_open.json b/test/data/rfc8259/n_object_trailing_comment_slash_open.json similarity index 100% rename from test/RFC8259JsonData/n_object_trailing_comment_slash_open.json rename to test/data/rfc8259/n_object_trailing_comment_slash_open.json diff --git a/test/RFC8259JsonData/n_object_trailing_comment_slash_open_incomplete.json b/test/data/rfc8259/n_object_trailing_comment_slash_open_incomplete.json similarity index 100% rename from test/RFC8259JsonData/n_object_trailing_comment_slash_open_incomplete.json rename to test/data/rfc8259/n_object_trailing_comment_slash_open_incomplete.json diff --git a/test/RFC8259JsonData/n_object_two_commas_in_a_row.json b/test/data/rfc8259/n_object_two_commas_in_a_row.json similarity index 100% rename from test/RFC8259JsonData/n_object_two_commas_in_a_row.json rename to test/data/rfc8259/n_object_two_commas_in_a_row.json diff --git a/test/RFC8259JsonData/n_object_unquoted_key.json b/test/data/rfc8259/n_object_unquoted_key.json similarity index 100% rename from test/RFC8259JsonData/n_object_unquoted_key.json rename to test/data/rfc8259/n_object_unquoted_key.json diff --git a/test/RFC8259JsonData/n_object_unterminated-value.json b/test/data/rfc8259/n_object_unterminated-value.json similarity index 100% rename from test/RFC8259JsonData/n_object_unterminated-value.json rename to test/data/rfc8259/n_object_unterminated-value.json diff --git a/test/RFC8259JsonData/n_object_with_single_string.json b/test/data/rfc8259/n_object_with_single_string.json similarity index 100% rename from test/RFC8259JsonData/n_object_with_single_string.json rename to test/data/rfc8259/n_object_with_single_string.json diff --git a/test/RFC8259JsonData/n_object_with_trailing_garbage.json b/test/data/rfc8259/n_object_with_trailing_garbage.json similarity index 100% rename from test/RFC8259JsonData/n_object_with_trailing_garbage.json rename to test/data/rfc8259/n_object_with_trailing_garbage.json diff --git a/test/RFC8259JsonData/n_single_space.json b/test/data/rfc8259/n_single_space.json similarity index 100% rename from test/RFC8259JsonData/n_single_space.json rename to test/data/rfc8259/n_single_space.json diff --git a/test/RFC8259JsonData/n_string_1_surrogate_then_escape.json b/test/data/rfc8259/n_string_1_surrogate_then_escape.json similarity index 100% rename from test/RFC8259JsonData/n_string_1_surrogate_then_escape.json rename to test/data/rfc8259/n_string_1_surrogate_then_escape.json diff --git a/test/RFC8259JsonData/n_string_1_surrogate_then_escape_u.json b/test/data/rfc8259/n_string_1_surrogate_then_escape_u.json similarity index 100% rename from test/RFC8259JsonData/n_string_1_surrogate_then_escape_u.json rename to test/data/rfc8259/n_string_1_surrogate_then_escape_u.json diff --git a/test/RFC8259JsonData/n_string_1_surrogate_then_escape_u1.json b/test/data/rfc8259/n_string_1_surrogate_then_escape_u1.json similarity index 100% rename from test/RFC8259JsonData/n_string_1_surrogate_then_escape_u1.json rename to test/data/rfc8259/n_string_1_surrogate_then_escape_u1.json diff --git a/test/RFC8259JsonData/n_string_1_surrogate_then_escape_u1x.json b/test/data/rfc8259/n_string_1_surrogate_then_escape_u1x.json similarity index 100% rename from test/RFC8259JsonData/n_string_1_surrogate_then_escape_u1x.json rename to test/data/rfc8259/n_string_1_surrogate_then_escape_u1x.json diff --git a/test/RFC8259JsonData/n_string_accentuated_char_no_quotes.json b/test/data/rfc8259/n_string_accentuated_char_no_quotes.json similarity index 100% rename from test/RFC8259JsonData/n_string_accentuated_char_no_quotes.json rename to test/data/rfc8259/n_string_accentuated_char_no_quotes.json diff --git a/test/RFC8259JsonData/n_string_backslash_00.json b/test/data/rfc8259/n_string_backslash_00.json similarity index 100% rename from test/RFC8259JsonData/n_string_backslash_00.json rename to test/data/rfc8259/n_string_backslash_00.json diff --git a/test/RFC8259JsonData/n_string_escape_x.json b/test/data/rfc8259/n_string_escape_x.json similarity index 100% rename from test/RFC8259JsonData/n_string_escape_x.json rename to test/data/rfc8259/n_string_escape_x.json diff --git a/test/RFC8259JsonData/n_string_escaped_backslash_bad.json b/test/data/rfc8259/n_string_escaped_backslash_bad.json similarity index 100% rename from test/RFC8259JsonData/n_string_escaped_backslash_bad.json rename to test/data/rfc8259/n_string_escaped_backslash_bad.json diff --git a/test/RFC8259JsonData/n_string_escaped_ctrl_char_tab.json b/test/data/rfc8259/n_string_escaped_ctrl_char_tab.json similarity index 100% rename from test/RFC8259JsonData/n_string_escaped_ctrl_char_tab.json rename to test/data/rfc8259/n_string_escaped_ctrl_char_tab.json diff --git a/test/RFC8259JsonData/n_string_escaped_emoji.json b/test/data/rfc8259/n_string_escaped_emoji.json similarity index 100% rename from test/RFC8259JsonData/n_string_escaped_emoji.json rename to test/data/rfc8259/n_string_escaped_emoji.json diff --git a/test/RFC8259JsonData/n_string_incomplete_escape.json b/test/data/rfc8259/n_string_incomplete_escape.json similarity index 100% rename from test/RFC8259JsonData/n_string_incomplete_escape.json rename to test/data/rfc8259/n_string_incomplete_escape.json diff --git a/test/RFC8259JsonData/n_string_incomplete_escaped_character.json b/test/data/rfc8259/n_string_incomplete_escaped_character.json similarity index 100% rename from test/RFC8259JsonData/n_string_incomplete_escaped_character.json rename to test/data/rfc8259/n_string_incomplete_escaped_character.json diff --git a/test/RFC8259JsonData/n_string_incomplete_surrogate.json b/test/data/rfc8259/n_string_incomplete_surrogate.json similarity index 100% rename from test/RFC8259JsonData/n_string_incomplete_surrogate.json rename to test/data/rfc8259/n_string_incomplete_surrogate.json diff --git a/test/RFC8259JsonData/n_string_incomplete_surrogate_escape_invalid.json b/test/data/rfc8259/n_string_incomplete_surrogate_escape_invalid.json similarity index 100% rename from test/RFC8259JsonData/n_string_incomplete_surrogate_escape_invalid.json rename to test/data/rfc8259/n_string_incomplete_surrogate_escape_invalid.json diff --git a/test/RFC8259JsonData/n_string_invalid-utf-8-in-escape.json b/test/data/rfc8259/n_string_invalid-utf-8-in-escape.json similarity index 100% rename from test/RFC8259JsonData/n_string_invalid-utf-8-in-escape.json rename to test/data/rfc8259/n_string_invalid-utf-8-in-escape.json diff --git a/test/RFC8259JsonData/n_string_invalid_backslash_esc.json b/test/data/rfc8259/n_string_invalid_backslash_esc.json similarity index 100% rename from test/RFC8259JsonData/n_string_invalid_backslash_esc.json rename to test/data/rfc8259/n_string_invalid_backslash_esc.json diff --git a/test/RFC8259JsonData/n_string_invalid_unicode_escape.json b/test/data/rfc8259/n_string_invalid_unicode_escape.json similarity index 100% rename from test/RFC8259JsonData/n_string_invalid_unicode_escape.json rename to test/data/rfc8259/n_string_invalid_unicode_escape.json diff --git a/test/RFC8259JsonData/n_string_invalid_utf8_after_escape.json b/test/data/rfc8259/n_string_invalid_utf8_after_escape.json similarity index 100% rename from test/RFC8259JsonData/n_string_invalid_utf8_after_escape.json rename to test/data/rfc8259/n_string_invalid_utf8_after_escape.json diff --git a/test/RFC8259JsonData/n_string_leading_uescaped_thinspace.json b/test/data/rfc8259/n_string_leading_uescaped_thinspace.json similarity index 100% rename from test/RFC8259JsonData/n_string_leading_uescaped_thinspace.json rename to test/data/rfc8259/n_string_leading_uescaped_thinspace.json diff --git a/test/RFC8259JsonData/n_string_no_quotes_with_bad_escape.json b/test/data/rfc8259/n_string_no_quotes_with_bad_escape.json similarity index 100% rename from test/RFC8259JsonData/n_string_no_quotes_with_bad_escape.json rename to test/data/rfc8259/n_string_no_quotes_with_bad_escape.json diff --git a/test/RFC8259JsonData/n_string_single_doublequote.json b/test/data/rfc8259/n_string_single_doublequote.json similarity index 100% rename from test/RFC8259JsonData/n_string_single_doublequote.json rename to test/data/rfc8259/n_string_single_doublequote.json diff --git a/test/RFC8259JsonData/n_string_single_quote.json b/test/data/rfc8259/n_string_single_quote.json similarity index 100% rename from test/RFC8259JsonData/n_string_single_quote.json rename to test/data/rfc8259/n_string_single_quote.json diff --git a/test/RFC8259JsonData/n_string_single_string_no_double_quotes.json b/test/data/rfc8259/n_string_single_string_no_double_quotes.json similarity index 100% rename from test/RFC8259JsonData/n_string_single_string_no_double_quotes.json rename to test/data/rfc8259/n_string_single_string_no_double_quotes.json diff --git a/test/RFC8259JsonData/n_string_start_escape_unclosed.json b/test/data/rfc8259/n_string_start_escape_unclosed.json similarity index 100% rename from test/RFC8259JsonData/n_string_start_escape_unclosed.json rename to test/data/rfc8259/n_string_start_escape_unclosed.json diff --git a/test/RFC8259JsonData/n_string_unescaped_ctrl_char.json b/test/data/rfc8259/n_string_unescaped_ctrl_char.json similarity index 100% rename from test/RFC8259JsonData/n_string_unescaped_ctrl_char.json rename to test/data/rfc8259/n_string_unescaped_ctrl_char.json diff --git a/test/RFC8259JsonData/n_string_unescaped_newline.json b/test/data/rfc8259/n_string_unescaped_newline.json similarity index 100% rename from test/RFC8259JsonData/n_string_unescaped_newline.json rename to test/data/rfc8259/n_string_unescaped_newline.json diff --git a/test/RFC8259JsonData/n_string_unescaped_tab.json b/test/data/rfc8259/n_string_unescaped_tab.json similarity index 100% rename from test/RFC8259JsonData/n_string_unescaped_tab.json rename to test/data/rfc8259/n_string_unescaped_tab.json diff --git a/test/RFC8259JsonData/n_string_unicode_CapitalU.json b/test/data/rfc8259/n_string_unicode_CapitalU.json similarity index 100% rename from test/RFC8259JsonData/n_string_unicode_CapitalU.json rename to test/data/rfc8259/n_string_unicode_CapitalU.json diff --git a/test/RFC8259JsonData/n_string_with_trailing_garbage.json b/test/data/rfc8259/n_string_with_trailing_garbage.json similarity index 100% rename from test/RFC8259JsonData/n_string_with_trailing_garbage.json rename to test/data/rfc8259/n_string_with_trailing_garbage.json diff --git a/test/RFC8259JsonData/n_structure_100000_opening_arrays.json b/test/data/rfc8259/n_structure_100000_opening_arrays.json similarity index 100% rename from test/RFC8259JsonData/n_structure_100000_opening_arrays.json rename to test/data/rfc8259/n_structure_100000_opening_arrays.json diff --git a/test/RFC8259JsonData/n_structure_U+2060_word_joined.json b/test/data/rfc8259/n_structure_U+2060_word_joined.json similarity index 100% rename from test/RFC8259JsonData/n_structure_U+2060_word_joined.json rename to test/data/rfc8259/n_structure_U+2060_word_joined.json diff --git a/test/RFC8259JsonData/n_structure_UTF8_BOM_no_data.json b/test/data/rfc8259/n_structure_UTF8_BOM_no_data.json similarity index 100% rename from test/RFC8259JsonData/n_structure_UTF8_BOM_no_data.json rename to test/data/rfc8259/n_structure_UTF8_BOM_no_data.json diff --git a/test/RFC8259JsonData/n_structure_angle_bracket_..json b/test/data/rfc8259/n_structure_angle_bracket_..json similarity index 100% rename from test/RFC8259JsonData/n_structure_angle_bracket_..json rename to test/data/rfc8259/n_structure_angle_bracket_..json diff --git a/test/RFC8259JsonData/n_structure_angle_bracket_null.json b/test/data/rfc8259/n_structure_angle_bracket_null.json similarity index 100% rename from test/RFC8259JsonData/n_structure_angle_bracket_null.json rename to test/data/rfc8259/n_structure_angle_bracket_null.json diff --git a/test/RFC8259JsonData/n_structure_array_trailing_garbage.json b/test/data/rfc8259/n_structure_array_trailing_garbage.json similarity index 100% rename from test/RFC8259JsonData/n_structure_array_trailing_garbage.json rename to test/data/rfc8259/n_structure_array_trailing_garbage.json diff --git a/test/RFC8259JsonData/n_structure_array_with_extra_array_close.json b/test/data/rfc8259/n_structure_array_with_extra_array_close.json similarity index 100% rename from test/RFC8259JsonData/n_structure_array_with_extra_array_close.json rename to test/data/rfc8259/n_structure_array_with_extra_array_close.json diff --git a/test/RFC8259JsonData/n_structure_array_with_unclosed_string.json b/test/data/rfc8259/n_structure_array_with_unclosed_string.json similarity index 100% rename from test/RFC8259JsonData/n_structure_array_with_unclosed_string.json rename to test/data/rfc8259/n_structure_array_with_unclosed_string.json diff --git a/test/RFC8259JsonData/n_structure_ascii-unicode-identifier.json b/test/data/rfc8259/n_structure_ascii-unicode-identifier.json similarity index 100% rename from test/RFC8259JsonData/n_structure_ascii-unicode-identifier.json rename to test/data/rfc8259/n_structure_ascii-unicode-identifier.json diff --git a/test/RFC8259JsonData/n_structure_capitalized_True.json b/test/data/rfc8259/n_structure_capitalized_True.json similarity index 100% rename from test/RFC8259JsonData/n_structure_capitalized_True.json rename to test/data/rfc8259/n_structure_capitalized_True.json diff --git a/test/RFC8259JsonData/n_structure_close_unopened_array.json b/test/data/rfc8259/n_structure_close_unopened_array.json similarity index 100% rename from test/RFC8259JsonData/n_structure_close_unopened_array.json rename to test/data/rfc8259/n_structure_close_unopened_array.json diff --git a/test/RFC8259JsonData/n_structure_comma_instead_of_closing_brace.json b/test/data/rfc8259/n_structure_comma_instead_of_closing_brace.json similarity index 100% rename from test/RFC8259JsonData/n_structure_comma_instead_of_closing_brace.json rename to test/data/rfc8259/n_structure_comma_instead_of_closing_brace.json diff --git a/test/RFC8259JsonData/n_structure_double_array.json b/test/data/rfc8259/n_structure_double_array.json similarity index 100% rename from test/RFC8259JsonData/n_structure_double_array.json rename to test/data/rfc8259/n_structure_double_array.json diff --git a/test/RFC8259JsonData/n_structure_end_array.json b/test/data/rfc8259/n_structure_end_array.json similarity index 100% rename from test/RFC8259JsonData/n_structure_end_array.json rename to test/data/rfc8259/n_structure_end_array.json diff --git a/test/RFC8259JsonData/n_structure_incomplete_UTF8_BOM.json b/test/data/rfc8259/n_structure_incomplete_UTF8_BOM.json similarity index 100% rename from test/RFC8259JsonData/n_structure_incomplete_UTF8_BOM.json rename to test/data/rfc8259/n_structure_incomplete_UTF8_BOM.json diff --git a/test/RFC8259JsonData/n_structure_lone-invalid-utf-8.json b/test/data/rfc8259/n_structure_lone-invalid-utf-8.json similarity index 100% rename from test/RFC8259JsonData/n_structure_lone-invalid-utf-8.json rename to test/data/rfc8259/n_structure_lone-invalid-utf-8.json diff --git a/test/RFC8259JsonData/n_structure_lone-open-bracket.json b/test/data/rfc8259/n_structure_lone-open-bracket.json similarity index 100% rename from test/RFC8259JsonData/n_structure_lone-open-bracket.json rename to test/data/rfc8259/n_structure_lone-open-bracket.json diff --git a/test/RFC8259JsonData/n_structure_no_data.json b/test/data/rfc8259/n_structure_no_data.json similarity index 100% rename from test/RFC8259JsonData/n_structure_no_data.json rename to test/data/rfc8259/n_structure_no_data.json diff --git a/test/RFC8259JsonData/n_structure_null-byte-outside-string.json b/test/data/rfc8259/n_structure_null-byte-outside-string.json similarity index 100% rename from test/RFC8259JsonData/n_structure_null-byte-outside-string.json rename to test/data/rfc8259/n_structure_null-byte-outside-string.json diff --git a/test/RFC8259JsonData/n_structure_number_with_trailing_garbage.json b/test/data/rfc8259/n_structure_number_with_trailing_garbage.json similarity index 100% rename from test/RFC8259JsonData/n_structure_number_with_trailing_garbage.json rename to test/data/rfc8259/n_structure_number_with_trailing_garbage.json diff --git a/test/RFC8259JsonData/n_structure_object_followed_by_closing_object.json b/test/data/rfc8259/n_structure_object_followed_by_closing_object.json similarity index 100% rename from test/RFC8259JsonData/n_structure_object_followed_by_closing_object.json rename to test/data/rfc8259/n_structure_object_followed_by_closing_object.json diff --git a/test/RFC8259JsonData/n_structure_object_unclosed_no_value.json b/test/data/rfc8259/n_structure_object_unclosed_no_value.json similarity index 100% rename from test/RFC8259JsonData/n_structure_object_unclosed_no_value.json rename to test/data/rfc8259/n_structure_object_unclosed_no_value.json diff --git a/test/RFC8259JsonData/n_structure_object_with_comment.json b/test/data/rfc8259/n_structure_object_with_comment.json similarity index 100% rename from test/RFC8259JsonData/n_structure_object_with_comment.json rename to test/data/rfc8259/n_structure_object_with_comment.json diff --git a/test/RFC8259JsonData/n_structure_object_with_trailing_garbage.json b/test/data/rfc8259/n_structure_object_with_trailing_garbage.json similarity index 100% rename from test/RFC8259JsonData/n_structure_object_with_trailing_garbage.json rename to test/data/rfc8259/n_structure_object_with_trailing_garbage.json diff --git a/test/RFC8259JsonData/n_structure_open_array_apostrophe.json b/test/data/rfc8259/n_structure_open_array_apostrophe.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_array_apostrophe.json rename to test/data/rfc8259/n_structure_open_array_apostrophe.json diff --git a/test/RFC8259JsonData/n_structure_open_array_comma.json b/test/data/rfc8259/n_structure_open_array_comma.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_array_comma.json rename to test/data/rfc8259/n_structure_open_array_comma.json diff --git a/test/RFC8259JsonData/n_structure_open_array_object.json b/test/data/rfc8259/n_structure_open_array_object.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_array_object.json rename to test/data/rfc8259/n_structure_open_array_object.json diff --git a/test/RFC8259JsonData/n_structure_open_array_open_object.json b/test/data/rfc8259/n_structure_open_array_open_object.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_array_open_object.json rename to test/data/rfc8259/n_structure_open_array_open_object.json diff --git a/test/RFC8259JsonData/n_structure_open_array_open_string.json b/test/data/rfc8259/n_structure_open_array_open_string.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_array_open_string.json rename to test/data/rfc8259/n_structure_open_array_open_string.json diff --git a/test/RFC8259JsonData/n_structure_open_array_string.json b/test/data/rfc8259/n_structure_open_array_string.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_array_string.json rename to test/data/rfc8259/n_structure_open_array_string.json diff --git a/test/RFC8259JsonData/n_structure_open_object.json b/test/data/rfc8259/n_structure_open_object.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_object.json rename to test/data/rfc8259/n_structure_open_object.json diff --git a/test/RFC8259JsonData/n_structure_open_object_close_array.json b/test/data/rfc8259/n_structure_open_object_close_array.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_object_close_array.json rename to test/data/rfc8259/n_structure_open_object_close_array.json diff --git a/test/RFC8259JsonData/n_structure_open_object_comma.json b/test/data/rfc8259/n_structure_open_object_comma.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_object_comma.json rename to test/data/rfc8259/n_structure_open_object_comma.json diff --git a/test/RFC8259JsonData/n_structure_open_object_open_array.json b/test/data/rfc8259/n_structure_open_object_open_array.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_object_open_array.json rename to test/data/rfc8259/n_structure_open_object_open_array.json diff --git a/test/RFC8259JsonData/n_structure_open_object_open_string.json b/test/data/rfc8259/n_structure_open_object_open_string.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_object_open_string.json rename to test/data/rfc8259/n_structure_open_object_open_string.json diff --git a/test/RFC8259JsonData/n_structure_open_object_string_with_apostrophes.json b/test/data/rfc8259/n_structure_open_object_string_with_apostrophes.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_object_string_with_apostrophes.json rename to test/data/rfc8259/n_structure_open_object_string_with_apostrophes.json diff --git a/test/RFC8259JsonData/n_structure_open_open.json b/test/data/rfc8259/n_structure_open_open.json similarity index 100% rename from test/RFC8259JsonData/n_structure_open_open.json rename to test/data/rfc8259/n_structure_open_open.json diff --git a/test/RFC8259JsonData/n_structure_single_eacute.json b/test/data/rfc8259/n_structure_single_eacute.json similarity index 100% rename from test/RFC8259JsonData/n_structure_single_eacute.json rename to test/data/rfc8259/n_structure_single_eacute.json diff --git a/test/RFC8259JsonData/n_structure_single_star.json b/test/data/rfc8259/n_structure_single_star.json similarity index 100% rename from test/RFC8259JsonData/n_structure_single_star.json rename to test/data/rfc8259/n_structure_single_star.json diff --git a/test/RFC8259JsonData/n_structure_trailing_#.json b/test/data/rfc8259/n_structure_trailing_#.json similarity index 100% rename from test/RFC8259JsonData/n_structure_trailing_#.json rename to test/data/rfc8259/n_structure_trailing_#.json diff --git a/test/RFC8259JsonData/n_structure_uescaped_LF_before_string.json b/test/data/rfc8259/n_structure_uescaped_LF_before_string.json similarity index 100% rename from test/RFC8259JsonData/n_structure_uescaped_LF_before_string.json rename to test/data/rfc8259/n_structure_uescaped_LF_before_string.json diff --git a/test/RFC8259JsonData/n_structure_unclosed_array.json b/test/data/rfc8259/n_structure_unclosed_array.json similarity index 100% rename from test/RFC8259JsonData/n_structure_unclosed_array.json rename to test/data/rfc8259/n_structure_unclosed_array.json diff --git a/test/RFC8259JsonData/n_structure_unclosed_array_partial_null.json b/test/data/rfc8259/n_structure_unclosed_array_partial_null.json similarity index 100% rename from test/RFC8259JsonData/n_structure_unclosed_array_partial_null.json rename to test/data/rfc8259/n_structure_unclosed_array_partial_null.json diff --git a/test/RFC8259JsonData/n_structure_unclosed_array_unfinished_false.json b/test/data/rfc8259/n_structure_unclosed_array_unfinished_false.json similarity index 100% rename from test/RFC8259JsonData/n_structure_unclosed_array_unfinished_false.json rename to test/data/rfc8259/n_structure_unclosed_array_unfinished_false.json diff --git a/test/RFC8259JsonData/n_structure_unclosed_array_unfinished_true.json b/test/data/rfc8259/n_structure_unclosed_array_unfinished_true.json similarity index 100% rename from test/RFC8259JsonData/n_structure_unclosed_array_unfinished_true.json rename to test/data/rfc8259/n_structure_unclosed_array_unfinished_true.json diff --git a/test/RFC8259JsonData/n_structure_unclosed_object.json b/test/data/rfc8259/n_structure_unclosed_object.json similarity index 100% rename from test/RFC8259JsonData/n_structure_unclosed_object.json rename to test/data/rfc8259/n_structure_unclosed_object.json diff --git a/test/RFC8259JsonData/n_structure_unicode-identifier.json b/test/data/rfc8259/n_structure_unicode-identifier.json similarity index 100% rename from test/RFC8259JsonData/n_structure_unicode-identifier.json rename to test/data/rfc8259/n_structure_unicode-identifier.json diff --git a/test/RFC8259JsonData/n_structure_whitespace_U+2060_word_joiner.json b/test/data/rfc8259/n_structure_whitespace_U+2060_word_joiner.json similarity index 100% rename from test/RFC8259JsonData/n_structure_whitespace_U+2060_word_joiner.json rename to test/data/rfc8259/n_structure_whitespace_U+2060_word_joiner.json diff --git a/test/RFC8259JsonData/n_structure_whitespace_formfeed.json b/test/data/rfc8259/n_structure_whitespace_formfeed.json similarity index 100% rename from test/RFC8259JsonData/n_structure_whitespace_formfeed.json rename to test/data/rfc8259/n_structure_whitespace_formfeed.json diff --git a/test/RFC8259JsonData/y_1.json b/test/data/rfc8259/y_1.json similarity index 100% rename from test/RFC8259JsonData/y_1.json rename to test/data/rfc8259/y_1.json diff --git a/test/RFC8259JsonData/y_2.json b/test/data/rfc8259/y_2.json similarity index 100% rename from test/RFC8259JsonData/y_2.json rename to test/data/rfc8259/y_2.json diff --git a/test/RFC8259JsonData/y_3.json b/test/data/rfc8259/y_3.json similarity index 100% rename from test/RFC8259JsonData/y_3.json rename to test/data/rfc8259/y_3.json diff --git a/test/RFC8259JsonData/y_array_arraysWithSpaces.json b/test/data/rfc8259/y_array_arraysWithSpaces.json similarity index 100% rename from test/RFC8259JsonData/y_array_arraysWithSpaces.json rename to test/data/rfc8259/y_array_arraysWithSpaces.json diff --git a/test/RFC8259JsonData/y_array_empty-string.json b/test/data/rfc8259/y_array_empty-string.json similarity index 100% rename from test/RFC8259JsonData/y_array_empty-string.json rename to test/data/rfc8259/y_array_empty-string.json diff --git a/test/RFC8259JsonData/y_array_empty.json b/test/data/rfc8259/y_array_empty.json similarity index 100% rename from test/RFC8259JsonData/y_array_empty.json rename to test/data/rfc8259/y_array_empty.json diff --git a/test/RFC8259JsonData/y_array_ending_with_newline.json b/test/data/rfc8259/y_array_ending_with_newline.json similarity index 100% rename from test/RFC8259JsonData/y_array_ending_with_newline.json rename to test/data/rfc8259/y_array_ending_with_newline.json diff --git a/test/RFC8259JsonData/y_array_false.json b/test/data/rfc8259/y_array_false.json similarity index 100% rename from test/RFC8259JsonData/y_array_false.json rename to test/data/rfc8259/y_array_false.json diff --git a/test/RFC8259JsonData/y_array_heterogeneous.json b/test/data/rfc8259/y_array_heterogeneous.json similarity index 100% rename from test/RFC8259JsonData/y_array_heterogeneous.json rename to test/data/rfc8259/y_array_heterogeneous.json diff --git a/test/RFC8259JsonData/y_array_nesting.json b/test/data/rfc8259/y_array_nesting.json similarity index 100% rename from test/RFC8259JsonData/y_array_nesting.json rename to test/data/rfc8259/y_array_nesting.json diff --git a/test/RFC8259JsonData/y_array_null.json b/test/data/rfc8259/y_array_null.json similarity index 100% rename from test/RFC8259JsonData/y_array_null.json rename to test/data/rfc8259/y_array_null.json diff --git a/test/RFC8259JsonData/y_array_with_1_and_newline.json b/test/data/rfc8259/y_array_with_1_and_newline.json similarity index 100% rename from test/RFC8259JsonData/y_array_with_1_and_newline.json rename to test/data/rfc8259/y_array_with_1_and_newline.json diff --git a/test/RFC8259JsonData/y_array_with_leading_space.json b/test/data/rfc8259/y_array_with_leading_space.json similarity index 100% rename from test/RFC8259JsonData/y_array_with_leading_space.json rename to test/data/rfc8259/y_array_with_leading_space.json diff --git a/test/RFC8259JsonData/y_array_with_several_null.json b/test/data/rfc8259/y_array_with_several_null.json similarity index 100% rename from test/RFC8259JsonData/y_array_with_several_null.json rename to test/data/rfc8259/y_array_with_several_null.json diff --git a/test/RFC8259JsonData/y_array_with_trailing_space.json b/test/data/rfc8259/y_array_with_trailing_space.json similarity index 100% rename from test/RFC8259JsonData/y_array_with_trailing_space.json rename to test/data/rfc8259/y_array_with_trailing_space.json diff --git a/test/RFC8259JsonData/y_number.json b/test/data/rfc8259/y_number.json similarity index 100% rename from test/RFC8259JsonData/y_number.json rename to test/data/rfc8259/y_number.json diff --git a/test/RFC8259JsonData/y_number_0e+1.json b/test/data/rfc8259/y_number_0e+1.json similarity index 100% rename from test/RFC8259JsonData/y_number_0e+1.json rename to test/data/rfc8259/y_number_0e+1.json diff --git a/test/RFC8259JsonData/y_number_0e1.json b/test/data/rfc8259/y_number_0e1.json similarity index 100% rename from test/RFC8259JsonData/y_number_0e1.json rename to test/data/rfc8259/y_number_0e1.json diff --git a/test/RFC8259JsonData/y_number_after_space.json b/test/data/rfc8259/y_number_after_space.json similarity index 100% rename from test/RFC8259JsonData/y_number_after_space.json rename to test/data/rfc8259/y_number_after_space.json diff --git a/test/RFC8259JsonData/y_number_double_close_to_zero.json b/test/data/rfc8259/y_number_double_close_to_zero.json similarity index 100% rename from test/RFC8259JsonData/y_number_double_close_to_zero.json rename to test/data/rfc8259/y_number_double_close_to_zero.json diff --git a/test/RFC8259JsonData/y_number_int_with_exp.json b/test/data/rfc8259/y_number_int_with_exp.json similarity index 100% rename from test/RFC8259JsonData/y_number_int_with_exp.json rename to test/data/rfc8259/y_number_int_with_exp.json diff --git a/test/RFC8259JsonData/y_number_minus_zero.json b/test/data/rfc8259/y_number_minus_zero.json similarity index 100% rename from test/RFC8259JsonData/y_number_minus_zero.json rename to test/data/rfc8259/y_number_minus_zero.json diff --git a/test/RFC8259JsonData/y_number_negative_int.json b/test/data/rfc8259/y_number_negative_int.json similarity index 100% rename from test/RFC8259JsonData/y_number_negative_int.json rename to test/data/rfc8259/y_number_negative_int.json diff --git a/test/RFC8259JsonData/y_number_negative_one.json b/test/data/rfc8259/y_number_negative_one.json similarity index 100% rename from test/RFC8259JsonData/y_number_negative_one.json rename to test/data/rfc8259/y_number_negative_one.json diff --git a/test/RFC8259JsonData/y_number_negative_zero.json b/test/data/rfc8259/y_number_negative_zero.json similarity index 100% rename from test/RFC8259JsonData/y_number_negative_zero.json rename to test/data/rfc8259/y_number_negative_zero.json diff --git a/test/RFC8259JsonData/y_number_real_capital_e.json b/test/data/rfc8259/y_number_real_capital_e.json similarity index 100% rename from test/RFC8259JsonData/y_number_real_capital_e.json rename to test/data/rfc8259/y_number_real_capital_e.json diff --git a/test/RFC8259JsonData/y_number_real_capital_e_neg_exp.json b/test/data/rfc8259/y_number_real_capital_e_neg_exp.json similarity index 100% rename from test/RFC8259JsonData/y_number_real_capital_e_neg_exp.json rename to test/data/rfc8259/y_number_real_capital_e_neg_exp.json diff --git a/test/RFC8259JsonData/y_number_real_capital_e_pos_exp.json b/test/data/rfc8259/y_number_real_capital_e_pos_exp.json similarity index 100% rename from test/RFC8259JsonData/y_number_real_capital_e_pos_exp.json rename to test/data/rfc8259/y_number_real_capital_e_pos_exp.json diff --git a/test/RFC8259JsonData/y_number_real_exponent.json b/test/data/rfc8259/y_number_real_exponent.json similarity index 100% rename from test/RFC8259JsonData/y_number_real_exponent.json rename to test/data/rfc8259/y_number_real_exponent.json diff --git a/test/RFC8259JsonData/y_number_real_fraction_exponent.json b/test/data/rfc8259/y_number_real_fraction_exponent.json similarity index 100% rename from test/RFC8259JsonData/y_number_real_fraction_exponent.json rename to test/data/rfc8259/y_number_real_fraction_exponent.json diff --git a/test/RFC8259JsonData/y_number_real_neg_exp.json b/test/data/rfc8259/y_number_real_neg_exp.json similarity index 100% rename from test/RFC8259JsonData/y_number_real_neg_exp.json rename to test/data/rfc8259/y_number_real_neg_exp.json diff --git a/test/RFC8259JsonData/y_number_real_pos_exponent.json b/test/data/rfc8259/y_number_real_pos_exponent.json similarity index 100% rename from test/RFC8259JsonData/y_number_real_pos_exponent.json rename to test/data/rfc8259/y_number_real_pos_exponent.json diff --git a/test/RFC8259JsonData/y_number_simple_int.json b/test/data/rfc8259/y_number_simple_int.json similarity index 100% rename from test/RFC8259JsonData/y_number_simple_int.json rename to test/data/rfc8259/y_number_simple_int.json diff --git a/test/RFC8259JsonData/y_number_simple_real.json b/test/data/rfc8259/y_number_simple_real.json similarity index 100% rename from test/RFC8259JsonData/y_number_simple_real.json rename to test/data/rfc8259/y_number_simple_real.json diff --git a/test/RFC8259JsonData/y_object.json b/test/data/rfc8259/y_object.json similarity index 100% rename from test/RFC8259JsonData/y_object.json rename to test/data/rfc8259/y_object.json diff --git a/test/RFC8259JsonData/y_object_basic.json b/test/data/rfc8259/y_object_basic.json similarity index 100% rename from test/RFC8259JsonData/y_object_basic.json rename to test/data/rfc8259/y_object_basic.json diff --git a/test/RFC8259JsonData/y_object_duplicated_key.json b/test/data/rfc8259/y_object_duplicated_key.json similarity index 100% rename from test/RFC8259JsonData/y_object_duplicated_key.json rename to test/data/rfc8259/y_object_duplicated_key.json diff --git a/test/RFC8259JsonData/y_object_duplicated_key_and_value.json b/test/data/rfc8259/y_object_duplicated_key_and_value.json similarity index 100% rename from test/RFC8259JsonData/y_object_duplicated_key_and_value.json rename to test/data/rfc8259/y_object_duplicated_key_and_value.json diff --git a/test/RFC8259JsonData/y_object_empty.json b/test/data/rfc8259/y_object_empty.json similarity index 100% rename from test/RFC8259JsonData/y_object_empty.json rename to test/data/rfc8259/y_object_empty.json diff --git a/test/RFC8259JsonData/y_object_empty_key.json b/test/data/rfc8259/y_object_empty_key.json similarity index 100% rename from test/RFC8259JsonData/y_object_empty_key.json rename to test/data/rfc8259/y_object_empty_key.json diff --git a/test/RFC8259JsonData/y_object_escaped_null_in_key.json b/test/data/rfc8259/y_object_escaped_null_in_key.json similarity index 100% rename from test/RFC8259JsonData/y_object_escaped_null_in_key.json rename to test/data/rfc8259/y_object_escaped_null_in_key.json diff --git a/test/RFC8259JsonData/y_object_extreme_numbers.json b/test/data/rfc8259/y_object_extreme_numbers.json similarity index 100% rename from test/RFC8259JsonData/y_object_extreme_numbers.json rename to test/data/rfc8259/y_object_extreme_numbers.json diff --git a/test/RFC8259JsonData/y_object_long_strings.json b/test/data/rfc8259/y_object_long_strings.json similarity index 100% rename from test/RFC8259JsonData/y_object_long_strings.json rename to test/data/rfc8259/y_object_long_strings.json diff --git a/test/RFC8259JsonData/y_object_simple.json b/test/data/rfc8259/y_object_simple.json similarity index 100% rename from test/RFC8259JsonData/y_object_simple.json rename to test/data/rfc8259/y_object_simple.json diff --git a/test/RFC8259JsonData/y_object_string_unicode.json b/test/data/rfc8259/y_object_string_unicode.json similarity index 100% rename from test/RFC8259JsonData/y_object_string_unicode.json rename to test/data/rfc8259/y_object_string_unicode.json diff --git a/test/RFC8259JsonData/y_object_with_newlines.json b/test/data/rfc8259/y_object_with_newlines.json similarity index 100% rename from test/RFC8259JsonData/y_object_with_newlines.json rename to test/data/rfc8259/y_object_with_newlines.json diff --git a/test/RFC8259JsonData/y_string_1_2_3_bytes_UTF-8_sequences.json b/test/data/rfc8259/y_string_1_2_3_bytes_UTF-8_sequences.json similarity index 100% rename from test/RFC8259JsonData/y_string_1_2_3_bytes_UTF-8_sequences.json rename to test/data/rfc8259/y_string_1_2_3_bytes_UTF-8_sequences.json diff --git a/test/RFC8259JsonData/y_string_accepted_surrogate_pair.json b/test/data/rfc8259/y_string_accepted_surrogate_pair.json similarity index 100% rename from test/RFC8259JsonData/y_string_accepted_surrogate_pair.json rename to test/data/rfc8259/y_string_accepted_surrogate_pair.json diff --git a/test/RFC8259JsonData/y_string_accepted_surrogate_pairs.json b/test/data/rfc8259/y_string_accepted_surrogate_pairs.json similarity index 100% rename from test/RFC8259JsonData/y_string_accepted_surrogate_pairs.json rename to test/data/rfc8259/y_string_accepted_surrogate_pairs.json diff --git a/test/RFC8259JsonData/y_string_allowed_escapes.json b/test/data/rfc8259/y_string_allowed_escapes.json similarity index 100% rename from test/RFC8259JsonData/y_string_allowed_escapes.json rename to test/data/rfc8259/y_string_allowed_escapes.json diff --git a/test/RFC8259JsonData/y_string_backslash_and_u_escaped_zero.json b/test/data/rfc8259/y_string_backslash_and_u_escaped_zero.json similarity index 100% rename from test/RFC8259JsonData/y_string_backslash_and_u_escaped_zero.json rename to test/data/rfc8259/y_string_backslash_and_u_escaped_zero.json diff --git a/test/RFC8259JsonData/y_string_backslash_doublequotes.json b/test/data/rfc8259/y_string_backslash_doublequotes.json similarity index 100% rename from test/RFC8259JsonData/y_string_backslash_doublequotes.json rename to test/data/rfc8259/y_string_backslash_doublequotes.json diff --git a/test/RFC8259JsonData/y_string_comments.json b/test/data/rfc8259/y_string_comments.json similarity index 100% rename from test/RFC8259JsonData/y_string_comments.json rename to test/data/rfc8259/y_string_comments.json diff --git a/test/RFC8259JsonData/y_string_double_escape_a.json b/test/data/rfc8259/y_string_double_escape_a.json similarity index 100% rename from test/RFC8259JsonData/y_string_double_escape_a.json rename to test/data/rfc8259/y_string_double_escape_a.json diff --git a/test/RFC8259JsonData/y_string_double_escape_n.json b/test/data/rfc8259/y_string_double_escape_n.json similarity index 100% rename from test/RFC8259JsonData/y_string_double_escape_n.json rename to test/data/rfc8259/y_string_double_escape_n.json diff --git a/test/RFC8259JsonData/y_string_escaped_control_character.json b/test/data/rfc8259/y_string_escaped_control_character.json similarity index 100% rename from test/RFC8259JsonData/y_string_escaped_control_character.json rename to test/data/rfc8259/y_string_escaped_control_character.json diff --git a/test/RFC8259JsonData/y_string_escaped_noncharacter.json b/test/data/rfc8259/y_string_escaped_noncharacter.json similarity index 100% rename from test/RFC8259JsonData/y_string_escaped_noncharacter.json rename to test/data/rfc8259/y_string_escaped_noncharacter.json diff --git a/test/RFC8259JsonData/y_string_in_array.json b/test/data/rfc8259/y_string_in_array.json similarity index 100% rename from test/RFC8259JsonData/y_string_in_array.json rename to test/data/rfc8259/y_string_in_array.json diff --git a/test/RFC8259JsonData/y_string_in_array_with_leading_space.json b/test/data/rfc8259/y_string_in_array_with_leading_space.json similarity index 100% rename from test/RFC8259JsonData/y_string_in_array_with_leading_space.json rename to test/data/rfc8259/y_string_in_array_with_leading_space.json diff --git a/test/RFC8259JsonData/y_string_last_surrogates_1_and_2.json b/test/data/rfc8259/y_string_last_surrogates_1_and_2.json similarity index 100% rename from test/RFC8259JsonData/y_string_last_surrogates_1_and_2.json rename to test/data/rfc8259/y_string_last_surrogates_1_and_2.json diff --git a/test/RFC8259JsonData/y_string_nbsp_uescaped.json b/test/data/rfc8259/y_string_nbsp_uescaped.json similarity index 100% rename from test/RFC8259JsonData/y_string_nbsp_uescaped.json rename to test/data/rfc8259/y_string_nbsp_uescaped.json diff --git a/test/RFC8259JsonData/y_string_nonCharacterInUTF-8_U+10FFFF.json b/test/data/rfc8259/y_string_nonCharacterInUTF-8_U+10FFFF.json similarity index 100% rename from test/RFC8259JsonData/y_string_nonCharacterInUTF-8_U+10FFFF.json rename to test/data/rfc8259/y_string_nonCharacterInUTF-8_U+10FFFF.json diff --git a/test/RFC8259JsonData/y_string_nonCharacterInUTF-8_U+FFFF.json b/test/data/rfc8259/y_string_nonCharacterInUTF-8_U+FFFF.json similarity index 100% rename from test/RFC8259JsonData/y_string_nonCharacterInUTF-8_U+FFFF.json rename to test/data/rfc8259/y_string_nonCharacterInUTF-8_U+FFFF.json diff --git a/test/RFC8259JsonData/y_string_null_escape.json b/test/data/rfc8259/y_string_null_escape.json similarity index 100% rename from test/RFC8259JsonData/y_string_null_escape.json rename to test/data/rfc8259/y_string_null_escape.json diff --git a/test/RFC8259JsonData/y_string_one-byte-utf-8.json b/test/data/rfc8259/y_string_one-byte-utf-8.json similarity index 100% rename from test/RFC8259JsonData/y_string_one-byte-utf-8.json rename to test/data/rfc8259/y_string_one-byte-utf-8.json diff --git a/test/RFC8259JsonData/y_string_pi.json b/test/data/rfc8259/y_string_pi.json similarity index 100% rename from test/RFC8259JsonData/y_string_pi.json rename to test/data/rfc8259/y_string_pi.json diff --git a/test/RFC8259JsonData/y_string_reservedCharacterInUTF-8_U+1BFFF.json b/test/data/rfc8259/y_string_reservedCharacterInUTF-8_U+1BFFF.json similarity index 100% rename from test/RFC8259JsonData/y_string_reservedCharacterInUTF-8_U+1BFFF.json rename to test/data/rfc8259/y_string_reservedCharacterInUTF-8_U+1BFFF.json diff --git a/test/RFC8259JsonData/y_string_simple_ascii.json b/test/data/rfc8259/y_string_simple_ascii.json similarity index 100% rename from test/RFC8259JsonData/y_string_simple_ascii.json rename to test/data/rfc8259/y_string_simple_ascii.json diff --git a/test/RFC8259JsonData/y_string_space.json b/test/data/rfc8259/y_string_space.json similarity index 100% rename from test/RFC8259JsonData/y_string_space.json rename to test/data/rfc8259/y_string_space.json diff --git a/test/RFC8259JsonData/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/test/data/rfc8259/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json similarity index 100% rename from test/RFC8259JsonData/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json rename to test/data/rfc8259/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json diff --git a/test/RFC8259JsonData/y_string_three-byte-utf-8.json b/test/data/rfc8259/y_string_three-byte-utf-8.json similarity index 100% rename from test/RFC8259JsonData/y_string_three-byte-utf-8.json rename to test/data/rfc8259/y_string_three-byte-utf-8.json diff --git a/test/RFC8259JsonData/y_string_two-byte-utf-8.json b/test/data/rfc8259/y_string_two-byte-utf-8.json similarity index 100% rename from test/RFC8259JsonData/y_string_two-byte-utf-8.json rename to test/data/rfc8259/y_string_two-byte-utf-8.json diff --git a/test/RFC8259JsonData/y_string_u+2028_line_sep.json b/test/data/rfc8259/y_string_u+2028_line_sep.json similarity index 100% rename from test/RFC8259JsonData/y_string_u+2028_line_sep.json rename to test/data/rfc8259/y_string_u+2028_line_sep.json diff --git a/test/RFC8259JsonData/y_string_u+2029_par_sep.json b/test/data/rfc8259/y_string_u+2029_par_sep.json similarity index 100% rename from test/RFC8259JsonData/y_string_u+2029_par_sep.json rename to test/data/rfc8259/y_string_u+2029_par_sep.json diff --git a/test/RFC8259JsonData/y_string_uEscape.json b/test/data/rfc8259/y_string_uEscape.json similarity index 100% rename from test/RFC8259JsonData/y_string_uEscape.json rename to test/data/rfc8259/y_string_uEscape.json diff --git a/test/RFC8259JsonData/y_string_uescaped_newline.json b/test/data/rfc8259/y_string_uescaped_newline.json similarity index 100% rename from test/RFC8259JsonData/y_string_uescaped_newline.json rename to test/data/rfc8259/y_string_uescaped_newline.json diff --git a/test/RFC8259JsonData/y_string_unescaped_char_delete.json b/test/data/rfc8259/y_string_unescaped_char_delete.json similarity index 100% rename from test/RFC8259JsonData/y_string_unescaped_char_delete.json rename to test/data/rfc8259/y_string_unescaped_char_delete.json diff --git a/test/RFC8259JsonData/y_string_unicode.json b/test/data/rfc8259/y_string_unicode.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode.json rename to test/data/rfc8259/y_string_unicode.json diff --git a/test/RFC8259JsonData/y_string_unicodeEscapedBackslash.json b/test/data/rfc8259/y_string_unicodeEscapedBackslash.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicodeEscapedBackslash.json rename to test/data/rfc8259/y_string_unicodeEscapedBackslash.json diff --git a/test/RFC8259JsonData/y_string_unicode_2.json b/test/data/rfc8259/y_string_unicode_2.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode_2.json rename to test/data/rfc8259/y_string_unicode_2.json diff --git a/test/RFC8259JsonData/y_string_unicode_U+10FFFE_nonchar.json b/test/data/rfc8259/y_string_unicode_U+10FFFE_nonchar.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode_U+10FFFE_nonchar.json rename to test/data/rfc8259/y_string_unicode_U+10FFFE_nonchar.json diff --git a/test/RFC8259JsonData/y_string_unicode_U+1FFFE_nonchar.json b/test/data/rfc8259/y_string_unicode_U+1FFFE_nonchar.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode_U+1FFFE_nonchar.json rename to test/data/rfc8259/y_string_unicode_U+1FFFE_nonchar.json diff --git a/test/RFC8259JsonData/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/test/data/rfc8259/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json rename to test/data/rfc8259/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json diff --git a/test/RFC8259JsonData/y_string_unicode_U+2064_invisible_plus.json b/test/data/rfc8259/y_string_unicode_U+2064_invisible_plus.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode_U+2064_invisible_plus.json rename to test/data/rfc8259/y_string_unicode_U+2064_invisible_plus.json diff --git a/test/RFC8259JsonData/y_string_unicode_U+FDD0_nonchar.json b/test/data/rfc8259/y_string_unicode_U+FDD0_nonchar.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode_U+FDD0_nonchar.json rename to test/data/rfc8259/y_string_unicode_U+FDD0_nonchar.json diff --git a/test/RFC8259JsonData/y_string_unicode_U+FFFE_nonchar.json b/test/data/rfc8259/y_string_unicode_U+FFFE_nonchar.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode_U+FFFE_nonchar.json rename to test/data/rfc8259/y_string_unicode_U+FFFE_nonchar.json diff --git a/test/RFC8259JsonData/y_string_unicode_escaped_double_quote.json b/test/data/rfc8259/y_string_unicode_escaped_double_quote.json similarity index 100% rename from test/RFC8259JsonData/y_string_unicode_escaped_double_quote.json rename to test/data/rfc8259/y_string_unicode_escaped_double_quote.json diff --git a/test/RFC8259JsonData/y_string_utf8.json b/test/data/rfc8259/y_string_utf8.json similarity index 100% rename from test/RFC8259JsonData/y_string_utf8.json rename to test/data/rfc8259/y_string_utf8.json diff --git a/test/RFC8259JsonData/y_string_with_del_character.json b/test/data/rfc8259/y_string_with_del_character.json similarity index 100% rename from test/RFC8259JsonData/y_string_with_del_character.json rename to test/data/rfc8259/y_string_with_del_character.json diff --git a/test/RFC8259JsonData/y_structure_lonely_false.json b/test/data/rfc8259/y_structure_lonely_false.json similarity index 100% rename from test/RFC8259JsonData/y_structure_lonely_false.json rename to test/data/rfc8259/y_structure_lonely_false.json diff --git a/test/RFC8259JsonData/y_structure_lonely_int.json b/test/data/rfc8259/y_structure_lonely_int.json similarity index 100% rename from test/RFC8259JsonData/y_structure_lonely_int.json rename to test/data/rfc8259/y_structure_lonely_int.json diff --git a/test/RFC8259JsonData/y_structure_lonely_negative_real.json b/test/data/rfc8259/y_structure_lonely_negative_real.json similarity index 100% rename from test/RFC8259JsonData/y_structure_lonely_negative_real.json rename to test/data/rfc8259/y_structure_lonely_negative_real.json diff --git a/test/RFC8259JsonData/y_structure_lonely_null.json b/test/data/rfc8259/y_structure_lonely_null.json similarity index 100% rename from test/RFC8259JsonData/y_structure_lonely_null.json rename to test/data/rfc8259/y_structure_lonely_null.json diff --git a/test/RFC8259JsonData/y_structure_lonely_string.json b/test/data/rfc8259/y_structure_lonely_string.json similarity index 100% rename from test/RFC8259JsonData/y_structure_lonely_string.json rename to test/data/rfc8259/y_structure_lonely_string.json diff --git a/test/RFC8259JsonData/y_structure_lonely_true.json b/test/data/rfc8259/y_structure_lonely_true.json similarity index 100% rename from test/RFC8259JsonData/y_structure_lonely_true.json rename to test/data/rfc8259/y_structure_lonely_true.json diff --git a/test/RFC8259JsonData/y_structure_string_empty.json b/test/data/rfc8259/y_structure_string_empty.json similarity index 100% rename from test/RFC8259JsonData/y_structure_string_empty.json rename to test/data/rfc8259/y_structure_string_empty.json diff --git a/test/RFC8259JsonData/y_structure_trailing_newline.json b/test/data/rfc8259/y_structure_trailing_newline.json similarity index 100% rename from test/RFC8259JsonData/y_structure_trailing_newline.json rename to test/data/rfc8259/y_structure_trailing_newline.json diff --git a/test/RFC8259JsonData/y_structure_true_in_array.json b/test/data/rfc8259/y_structure_true_in_array.json similarity index 100% rename from test/RFC8259JsonData/y_structure_true_in_array.json rename to test/data/rfc8259/y_structure_true_in_array.json diff --git a/test/RFC8259JsonData/y_structure_whitespace_array.json b/test/data/rfc8259/y_structure_whitespace_array.json similarity index 100% rename from test/RFC8259JsonData/y_structure_whitespace_array.json rename to test/data/rfc8259/y_structure_whitespace_array.json diff --git a/externalModule/cJSON/cJSON.c b/test/externalModule/cJSON/cJSON.c similarity index 100% rename from externalModule/cJSON/cJSON.c rename to test/externalModule/cJSON/cJSON.c diff --git a/externalModule/cJSON/cJSON.h b/test/externalModule/cJSON/cJSON.h similarity index 100% rename from externalModule/cJSON/cJSON.h rename to test/externalModule/cJSON/cJSON.h diff --git a/test/externalModule/tlsf/rtthread.h b/test/externalModule/tlsf/rtthread.h new file mode 100644 index 0000000..5455d60 --- /dev/null +++ b/test/externalModule/tlsf/rtthread.h @@ -0,0 +1,14 @@ +#pragma once + +// !这个文件仅为了tlsf的测试 +#include +#include +#include +#include +#include +#include +#include + +#define rt_memcpy memcpy +#define RT_ASSERT(EX) assert(EX) +#define rt_always_inline static inline __attribute__((always_inline)) diff --git a/test/externalModule/tlsf/tlsf.c b/test/externalModule/tlsf/tlsf.c new file mode 100644 index 0000000..6b17ea8 --- /dev/null +++ b/test/externalModule/tlsf/tlsf.c @@ -0,0 +1,702 @@ +/* + * SPDX-FileCopyrightText: 2006-2016 Matthew Conte + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include "tlsf.h" + +#undef printf +#define printf(...) + +#include "tlsf_block_functions.h" +#include "tlsf_control_functions.h" + +/* +** Static assertion mechanism. +*/ + +#define _tlsf_glue2(x, y) x##y +#define _tlsf_glue(x, y) _tlsf_glue2(x, y) +#define tlsf_static_assert(exp) typedef char _tlsf_glue(static_assert, __LINE__)[(exp) ? 1 : -1] + +/* This code has been tested on 32- and 64-bit (LP/LLP) architectures. */ +tlsf_static_assert(sizeof(int) * CHAR_BIT == 32); +tlsf_static_assert(sizeof(size_t) * CHAR_BIT >= 32); +tlsf_static_assert(sizeof(size_t) * CHAR_BIT <= 64); + +/* Clear structure and point all empty lists at the null block. */ +static control_t *control_construct(control_t *control, size_t bytes) +{ + // check that the requested size can at least hold the control_t. This will allow us + // to fill in the field of control_t necessary to determine the final size of + // the metadata overhead and check that the requested size can hold + // this data and at least a block of minimum size + if (bytes < sizeof(control_t)) { return NULL; } + + /* Find the closest power of two for first layer */ + control->fl_index_max = 32 - __builtin_clz(bytes); + + /* Adapt second layer to the pool */ + if (bytes <= 64 * 1024) { control->sl_index_count_log2 = 3; } + else if (bytes <= 256 * 1024) { control->sl_index_count_log2 = 4; } + else + { + control->sl_index_count_log2 = 5; + } + + control->fl_index_shift = (control->sl_index_count_log2 + ALIGN_SIZE_LOG2); + control->sl_index_count = 1 << control->sl_index_count_log2; + control->fl_index_count = control->fl_index_max - control->fl_index_shift + 1; + control->small_block_size = 1 << control->fl_index_shift; + + // the total size fo the metadata overhead is the size of the control_t + // added to the size of the sl_bitmaps and the size of blocks + control->size = sizeof(control_t) + (sizeof(*control->sl_bitmap) * control->fl_index_count) + + (sizeof(*control->blocks) * (control->fl_index_count * control->sl_index_count)); + + // check that the requested size can hold the whole control structure and + // a small block at least + if (bytes < control->size + block_size_min) { return NULL; } + + control->block_null.next_free = &control->block_null; + control->block_null.prev_free = &control->block_null; + + control->fl_bitmap = 0; + control->sl_bitmap = align_ptr(control + 1, sizeof(*control->sl_bitmap)); + control->blocks = align_ptr(control->sl_bitmap + control->fl_index_count, sizeof(*control->blocks)); + + /* SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type. */ + tlsf_assert(sizeof(unsigned int) * CHAR_BIT >= control->sl_index_count && "CHAR_BIT less than sl_index_count"); + + /* Ensure we've properly tuned our sizes. */ + tlsf_assert(ALIGN_SIZE == control->small_block_size / control->sl_index_count); // ALIGN_SIZE does not match"); + + for (int i = 0; i < control->fl_index_count; ++i) + { + control->sl_bitmap[i] = 0; + for (int j = 0; j < control->sl_index_count; ++j) + { + control->blocks[i * control->sl_index_count + j] = &control->block_null; + } + } + + return control; +} + +/* +** Debugging utilities. +*/ + +typedef struct integrity_t +{ + int prev_status; + int status; +} integrity_t; + +#define tlsf_insist(x) \ + { \ + if (!(x)) { status--; } \ + } + +static bool integrity_walker(void *ptr, size_t size, int used, void *user) +{ + block_header_t *block = block_from_ptr(ptr); + integrity_t *integ = tlsf_cast(integrity_t *, user); + const int this_prev_status = block_is_prev_free(block) ? 1 : 0; + const int this_status = block_is_free(block) ? 1 : 0; + const size_t this_block_size = block_size(block); + + int status = 0; + tlsf_insist(integ->prev_status == this_prev_status && "prev status incorrect"); + tlsf_insist(size == this_block_size && "block size incorrect"); + + if (tlsf_check_hook != NULL) + { + /* block_size(block) returns the size of the usable memory when the block is allocated. + * As the block under test is free, we need to subtract to the block size the next_free + * and prev_free fields of the block header as they are not a part of the usable memory + * when the block is free. In addition, we also need to subtract the size of prev_phys_block + * as this field is in fact part of the current free block and not part of the next (allocated) + * block. Check the comments in block_split function for more details. + */ + const size_t actual_free_block_size = + used ? this_block_size : this_block_size - offsetof(block_header_t, next_free) - block_header_overhead; + + void *ptr_block = used ? (void *)block + block_start_offset : (void *)block + sizeof(block_header_t); + + tlsf_insist(tlsf_check_hook(ptr_block, actual_free_block_size, !used)); + } + + integ->prev_status = this_status; + integ->status += status; + + return true; +} + +int tlsf_check(tlsf_t tlsf) +{ + int i, j; + + control_t *control = tlsf_cast(control_t *, tlsf); + int status = 0; + + /* Check that the free lists and bitmaps are accurate. */ + for (i = 0; i < control->fl_index_count; ++i) + { + for (j = 0; j < control->sl_index_count; ++j) + { + const int fl_map = control->fl_bitmap & (1U << i); + const int sl_list = control->sl_bitmap[i]; + const int sl_map = sl_list & (1U << j); + const block_header_t *block = control->blocks[i * control->sl_index_count + j]; + + /* Check that first- and second-level lists agree. */ + if (!fl_map) { tlsf_insist(!sl_map && "second-level map must be null"); } + + if (!sl_map) + { + tlsf_insist(block == &control->block_null && "block list must be null"); + continue; + } + + /* Check that there is at least one free block. */ + tlsf_insist(sl_list && "no free blocks in second-level map"); + tlsf_insist(block != &control->block_null && "block should not be null"); + + while (block != &control->block_null) + { + int fli, sli; + const bool is_block_free = block_is_free(block); + tlsf_insist(is_block_free && "block should be free"); + tlsf_insist(!block_is_prev_free(block) && "blocks should have coalesced"); + tlsf_insist(!block_is_free(block_next(block)) && "blocks should have coalesced"); + tlsf_insist(block_is_prev_free(block_next(block)) && "block should be free"); + tlsf_insist(block_size(block) >= block_size_min && "block not minimum size"); + + mapping_insert(control, block_size(block), &fli, &sli); + tlsf_insist(fli == i && sli == j && "block size indexed in wrong list"); + + block = block->next_free; + } + } + } + + return status; +} + +#undef tlsf_insist + +static bool default_walker(void *ptr, size_t size, int used, void *user) +{ + (void)user; + printf("\t%p %s size: %x (%p)\n", ptr, used ? "used" : "free", (unsigned int)size, block_from_ptr(ptr)); + return true; +} + +void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void *user) +{ + tlsf_walker pool_walker = walker ? walker : default_walker; + block_header_t *block = offset_to_block(pool, -(int)block_header_overhead); + + bool ret_val = true; + while (block && !block_is_last(block) && ret_val == true) + { + ret_val = pool_walker(block_to_ptr(block), block_size(block), !block_is_free(block), user); + + if (ret_val == true) { block = block_next(block); } + } +} + +size_t tlsf_block_size(void *ptr) +{ + size_t size = 0; + if (ptr) + { + const block_header_t *block = block_from_ptr(ptr); + size = block_size(block); + } + return size; +} + +int tlsf_check_pool(pool_t pool) +{ + /* Check that the blocks are physically correct. */ + integrity_t integ = {0, 0}; + tlsf_walk_pool(pool, integrity_walker, &integ); + + return integ.status; +} + +size_t tlsf_fit_size(tlsf_t tlsf, size_t size) +{ + if (size == 0 || tlsf == NULL) { return 0; } + + control_t *control = tlsf_cast(control_t *, tlsf); + if (size < control->small_block_size) { return adjust_request_size(tlsf, size, ALIGN_SIZE); } + + /* because it's GoodFit, allocable size is one range lower */ + size_t sl_interval; + sl_interval = (1 << (32 - __builtin_clz(size) - 1)) / control->sl_index_count; + return size & ~(sl_interval - 1); +} + +/* +** Size of the TLSF structures in a given memory block passed to +** tlsf_create, equal to the size of a control_t +*/ +size_t tlsf_size(tlsf_t tlsf) +{ + if (tlsf == NULL) { return 0; } + control_t *control = tlsf_cast(control_t *, tlsf); + return control->size; +} + +/* +** Overhead of the TLSF structures in a given memory block passed to +** tlsf_add_pool, equal to the overhead of a free block and the +** sentinel block. +*/ +size_t tlsf_pool_overhead(void) { return 2 * block_header_overhead; } + +size_t tlsf_alloc_overhead(void) { return block_header_overhead; } + +pool_t tlsf_add_pool(tlsf_t tlsf, void *mem, size_t bytes) +{ + block_header_t *block; + block_header_t *next; + + const size_t pool_overhead = tlsf_pool_overhead(); + const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE); + + if (((ptrdiff_t)mem % ALIGN_SIZE) != 0) + { + printf("tlsf_add_pool: Memory must be aligned by %u bytes.\n", (unsigned int)ALIGN_SIZE); + return 0; + } + + if (pool_bytes < block_size_min || pool_bytes > tlsf_block_size_max(tlsf)) + { +#if defined(TLSF_64BIT) + printf("tlsf_add_pool: Memory size must be between 0x%x and 0x%x00 bytes.\n", + (unsigned int)(pool_overhead + block_size_min), (unsigned int)((pool_overhead + tlsf_block_size_max(tlsf)) / 256)); +#else + printf("tlsf_add_pool: Memory size must be between %u and %u bytes.\n", (unsigned int)(pool_overhead + block_size_min), + (unsigned int)(pool_overhead + tlsf_block_size_max(tlsf))); +#endif + return 0; + } + + /* + ** Create the main free block. Offset the start of the block slightly + ** so that the prev_phys_block field falls outside of the pool - + ** it will never be used. + */ + block = offset_to_block(mem, -(tlsfptr_t)block_header_overhead); + block_set_size(block, pool_bytes); + block_set_free(block); + block_set_prev_used(block); + block_insert(tlsf_cast(control_t *, tlsf), block); + + /* Split the block to create a zero-size sentinel block. */ + next = block_link_next(block); + block_set_size(next, 0); + block_set_used(next); + block_set_prev_free(next); + + return mem; +} + +void tlsf_remove_pool(tlsf_t tlsf, pool_t pool) +{ + control_t *control = tlsf_cast(control_t *, tlsf); + block_header_t *block = offset_to_block(pool, -(int)block_header_overhead); + + int fl = 0, sl = 0; + + tlsf_assert(block_is_free(block) && "block should be free"); + tlsf_assert(!block_is_free(block_next(block)) && "next block should not be free"); + tlsf_assert(block_size(block_next(block)) == 0 && "next block size should be zero"); + + mapping_insert(control, block_size(block), &fl, &sl); + remove_free_block(control, block, fl, sl); +} + +/* +** TLSF main interface. +*/ + +#if _DEBUG +int test_ffs_fls() +{ + /* Verify ffs/fls work properly. */ + int rv = 0; + rv += (tlsf_ffs(0) == -1) ? 0 : 0x1; + rv += (tlsf_fls(0) == -1) ? 0 : 0x2; + rv += (tlsf_ffs(1) == 0) ? 0 : 0x4; + rv += (tlsf_fls(1) == 0) ? 0 : 0x8; + rv += (tlsf_ffs(0x80000000) == 31) ? 0 : 0x10; + rv += (tlsf_ffs(0x80008000) == 15) ? 0 : 0x20; + rv += (tlsf_fls(0x80000008) == 31) ? 0 : 0x40; + rv += (tlsf_fls(0x7FFFFFFF) == 30) ? 0 : 0x80; + +#if defined(TLSF_64BIT) + rv += (tlsf_fls_sizet(0x80000000) == 31) ? 0 : 0x100; + rv += (tlsf_fls_sizet(0x100000000) == 32) ? 0 : 0x200; + rv += (tlsf_fls_sizet(0xffffffffffffffff) == 63) ? 0 : 0x400; +#endif + + if (rv) { printf("test_ffs_fls: %x ffs/fls tests failed.\n", rv); } + return rv; +} +#endif + +tlsf_t tlsf_create(void *mem, size_t max_bytes) +{ +#if _DEBUG + if (test_ffs_fls()) { return NULL; } +#endif + + if (mem == NULL) { return NULL; } + + if (((tlsfptr_t)mem % ALIGN_SIZE) != 0) + { + printf("tlsf_create: Memory must be aligned to %u bytes.\n", (unsigned int)ALIGN_SIZE); + return NULL; + } + + control_t *control_ptr = control_construct(tlsf_cast(control_t *, mem), max_bytes); + return tlsf_cast(tlsf_t, control_ptr); +} + +tlsf_t tlsf_create_with_pool(void *mem, size_t pool_bytes, size_t max_bytes) +{ + tlsf_t tlsf = tlsf_create(mem, max_bytes ? max_bytes : pool_bytes); + if (tlsf != NULL) + { + tlsf_add_pool(tlsf, (char *)mem + tlsf_size(tlsf), pool_bytes - tlsf_size(tlsf)); + control_t *control = tlsf_cast(control_t *, tlsf); + control->mem_rec.total = pool_bytes - tlsf_size(tlsf) - tlsf_pool_overhead(); + control->mem_rec.used = 0; + control->mem_rec.max_used = 0; + } + return tlsf; +} + +void tlsf_destroy(tlsf_t tlsf) +{ + /* Nothing to do. */ + (void)tlsf; +} + +pool_t tlsf_get_pool(tlsf_t tlsf) { return tlsf_cast(pool_t, (char *)tlsf + tlsf_size(tlsf)); } + +void *tlsf_malloc(tlsf_t tlsf, size_t size) +{ + control_t *control = tlsf_cast(control_t *, tlsf); + size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE); + // Returned size is 0 when the requested size is larger than the max block + // size. + if (adjust == 0) { return NULL; } + // block_locate_free() may adjust our allocated size further. + block_header_t *block = block_locate_free(control, &adjust); + return block_prepare_used(control, block, adjust); +} + +/** + * @brief Allocate memory of at least `size` bytes at a given address in the pool. + * + * @param tlsf TLSF structure to allocate memory from. + * @param size Minimum size, in bytes, of the memory to allocate + * @param address address at which the allocation must be done + * + * @return pointer to free memory or NULL in case of incapacity to perform the malloc + */ +void *tlsf_malloc_addr(tlsf_t tlsf, size_t size, void *address) +{ + control_t *control = tlsf_cast(control_t *, tlsf); + + /* adjust the address to be ALIGN_SIZE bytes aligned. */ + const uintptr_t addr_adjusted = align_down(tlsf_cast(uintptr_t, address), ALIGN_SIZE); + + /* adjust the size to be ALIGN_SIZE bytes aligned. Add to the size the difference + * between the requested address and the address_adjusted. */ + size_t size_adjusted = align_up(size + (tlsf_cast(uintptr_t, address) - addr_adjusted), ALIGN_SIZE); + + /* find the free block that starts before the address in the pool and is big enough + * to support the size of allocation at the given address */ + block_header_t *block = offset_to_block(tlsf_get_pool(tlsf), -(int)block_header_overhead); + + const char *alloc_start = tlsf_cast(char *, addr_adjusted); + const char *alloc_end = alloc_start + size_adjusted; + bool block_found = false; + do + { + const char *block_start = tlsf_cast(char *, block_to_ptr(block)); + const char *block_end = tlsf_cast(char *, block_to_ptr(block)) + block_size(block); + if (block_start <= alloc_start && block_end > alloc_start) + { + /* A: block_end >= alloc_end. B: block is free */ + if (block_end < alloc_end || !block_is_free(block)) + { + /* not(A) || not(B) + * We won't find another suitable block from this point on + * so we can break and return NULL */ + break; + } + /* A && B + * The block can fit the alloc and is located at a position allowing for the alloc + * to be placed at the given address. We can return from the while */ + block_found = true; + } + else if (!block_is_last(block)) + { + /* the block doesn't match the expected criteria, continue with the next block */ + block = block_next(block); + } + + } while (!block_is_last(block) && block_found == false); + + if (!block_found) { return NULL; } + + /* remove block from the free list since a part of it will be used */ + block_remove(control, block); + + /* trim any leading space or add the leading space to the overall requested size + * if the leading space is not big enough to store a block of minimum size */ + const size_t space_before_addr_adjusted = addr_adjusted - tlsf_cast(uintptr_t, block_to_ptr(block)); + block_header_t *return_block = block; + if (space_before_addr_adjusted >= block_size_min) + { + return_block = block_trim_free_leading(control, block, space_before_addr_adjusted); + } + else + { + size_adjusted += space_before_addr_adjusted; + } + + /* trim trailing space if any and return a pointer to the first usable byte allocated */ + return block_prepare_used(control, return_block, size_adjusted); +} + +/** + * @brief Allocate memory of at least `size` bytes where byte at `data_offset` will be aligned to `alignment`. + * + * This function will allocate memory pointed by `ptr`. However, the byte at `data_offset` of + * this piece of memory (i.e., byte at `ptr` + `data_offset`) will be aligned to `alignment`. + * This function is useful for allocating memory that will internally have a header, and the + * usable memory following the header (i.e. `ptr` + `data_offset`) must be aligned. + * + * For example, a call to `multi_heap_aligned_alloc_impl_offs(heap, 64, 256, 20)` will return a + * pointer `ptr` to free memory of minimum 64 bytes, where `ptr + 20` is aligned on `256`. + * So `(ptr + 20) % 256` equals 0. + * + * @param tlsf TLSF structure to allocate memory from. + * @param align Alignment for the returned pointer's offset. + * @param size Minimum size, in bytes, of the memory to allocate INCLUDING + * `data_offset` bytes. + * @param data_offset Offset to be aligned on `alignment`. This can be 0, in + * this case, the returned pointer will be aligned on + * `alignment`. If it is not a multiple of CPU word size, + * it will be aligned up to the closest multiple of it. + * + * @return pointer to free memory. + */ +void *tlsf_memalign_offs(tlsf_t tlsf, size_t align, size_t size, size_t data_offset) +{ + control_t *control = tlsf_cast(control_t *, tlsf); + const size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE); + const size_t off_adjust = align_up(data_offset, ALIGN_SIZE); + + /* + ** We must allocate an additional minimum block size bytes so that if + ** our free block will leave an alignment gap which is smaller, we can + ** trim a leading free block and release it back to the pool. We must + ** do this because the previous physical block is in use, therefore + ** the prev_phys_block field is not valid, and we can't simply adjust + ** the size of that block. + */ + const size_t gap_minimum = sizeof(block_header_t) + off_adjust; + /* The offset is included in both `adjust` and `gap_minimum`, so we + ** need to subtract it once. + */ + const size_t size_with_gap = adjust_request_size(tlsf, adjust + align + gap_minimum - off_adjust, align); + + /* + ** If alignment is less than or equal to base alignment, we're done, because + ** we are guaranteed that the size is at least sizeof(block_header_t), enough + ** to store next blocks' metadata. Plus, all pointers allocated will all be + ** aligned on a 4-byte bound, so ptr + data_offset will also have this + ** alignment constraint. Thus, the gap is not required. + ** If we requested 0 bytes, return null, as tlsf_malloc(0) does. + */ + size_t aligned_size = (adjust && align > ALIGN_SIZE) ? size_with_gap : adjust; + + block_header_t *block = block_locate_free(control, &aligned_size); + + /* This can't be a static assert. */ + tlsf_assert(sizeof(block_header_t) == block_size_min + block_header_overhead); + + if (block) + { + void *ptr = block_to_ptr(block); + void *aligned = align_ptr(ptr, align); + size_t gap = tlsf_cast(size_t, tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr)); + + /* + ** If gap size is too small or if there is no gap but we need one, + ** offset to next aligned boundary. + ** NOTE: No need for a gap if the alignment required is less than or is + ** equal to ALIGN_SIZE. + */ + if ((gap && gap < gap_minimum) || (!gap && off_adjust && align > ALIGN_SIZE)) + { + const size_t gap_remain = gap_minimum - gap; + const size_t offset = tlsf_max(gap_remain, align); + const void *next_aligned = tlsf_cast(void *, tlsf_cast(tlsfptr_t, aligned) + offset); + + aligned = align_ptr(next_aligned, align); + gap = tlsf_cast(size_t, tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr)); + } + + if (gap) + { + tlsf_assert(gap >= gap_minimum && "gap size too small"); + block = block_trim_free_leading(control, block, gap - off_adjust); + } + } + + /* Preparing the block will also the trailing free memory. */ + return block_prepare_used(control, block, adjust); +} + +/** + * @brief Same as `tlsf_memalign_offs` function but with a 0 offset. + * The pointer returned is aligned on `align`. + */ +void *tlsf_memalign(tlsf_t tlsf, size_t align, size_t size) { return tlsf_memalign_offs(tlsf, align, size, 0); } +void tlsf_memory_info(tlsf_t tlsf, size_t *total, size_t *used, size_t *max_used) +{ + control_t *control = tlsf_cast(control_t *, tlsf); + if (total) { *total = control->mem_rec.total; } + if (used) { *used = control->mem_rec.used; } + if (max_used) { *max_used = control->mem_rec.max_used; } +} +void tlsf_free(tlsf_t tlsf, void *ptr) +{ + /* Don't attempt to free a NULL pointer. */ + if (ptr) + { + control_t *control = tlsf_cast(control_t *, tlsf); + block_header_t *block = block_from_ptr(ptr); + tlsf_assert(!block_is_free(block) && "block already marked as free"); + + control->mem_rec.used -= (block_size(block) + tlsf_alloc_overhead()); + + block_mark_as_free(block); + block = block_merge_prev(control, block); + block = block_merge_next(control, block); + block_insert(control, block); + } +} + +/* +** The TLSF block information provides us with enough information to +** provide a reasonably intelligent implementation of realloc, growing or +** shrinking the currently allocated block as required. +** +** This routine handles the somewhat esoteric edge cases of realloc: +** - a non-zero size with a null pointer will behave like malloc +** - a zero size with a non-null pointer will behave like free +** - a request that cannot be satisfied will leave the original buffer +** untouched +** - an extended buffer size will leave the newly-allocated area with +** contents undefined +*/ +void *tlsf_realloc(tlsf_t tlsf, void *ptr, size_t size) +{ + control_t *control = tlsf_cast(control_t *, tlsf); + void *p = 0; + + /* Zero-size requests are treated as free. */ + if (ptr && size == 0) { tlsf_free(tlsf, ptr); } + /* Requests with NULL pointers are treated as malloc. */ + else if (!ptr) { p = tlsf_malloc(tlsf, size); } + else + { + block_header_t *block = block_from_ptr(ptr); + block_header_t *next = block_next(block); + + const size_t cursize = block_size(block); + const size_t combined = cursize + block_size(next) + block_header_overhead; + const size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE); + + // if adjust if equal to 0, the size is too big + if (adjust == 0) { return p; } + + tlsf_assert(!block_is_free(block) && "block already marked as free"); + + /* + ** If the next block is used, or when combined with the current + ** block, does not offer enough space, we must reallocate and copy. + */ + if (adjust > cursize && (!block_is_free(next) || adjust > combined)) + { + p = tlsf_malloc(tlsf, size); + if (p) + { + const size_t minsize = tlsf_min(cursize, size); + rt_memcpy(p, ptr, minsize); + tlsf_free(tlsf, ptr); + } + } + else + { + /* Do we need to expand to the next block? */ + if (adjust > cursize) + { + block_merge_next(control, block); + block_mark_as_used(block); + } + + /* Trim the resulting block and return the original pointer. */ + block_trim_used(control, block, adjust); + p = ptr; + + /* 更新统计信息:原地调整时需要修正 used */ + control->mem_rec.used += block_size(block); + control->mem_rec.used -= cursize; + + if(control->mem_rec.used > control->mem_rec.max_used) + control->mem_rec.max_used = control->mem_rec.used; + } + } + + return p; +} + +void *tlsf_find_containing_block(pool_t pool, void *ptr) +{ + block_header_t *block = offset_to_block(pool, -(int)block_header_overhead); + + while (block && !block_is_last(block)) + { + if (!block_is_free(block)) + { + void *block_end = block_to_ptr(block) + block_size(block); + if (block_to_ptr(block) <= ptr && block_end > ptr) + { + // we found the containing block, return + return block_to_ptr(block); + } + } + + block = block_next(block); + } + + return NULL; +} diff --git a/test/externalModule/tlsf/tlsf.h b/test/externalModule/tlsf/tlsf.h new file mode 100644 index 0000000..103e9ab --- /dev/null +++ b/test/externalModule/tlsf/tlsf.h @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2006-2016 Matthew Conte + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef INCLUDED_tlsf +#define INCLUDED_tlsf + +#include +#include +#include +#include "rtthread.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */ +/* pool_t: a block of memory that TLSF can manage. */ +typedef void* tlsf_t; +typedef void* pool_t; + +/* Create/destroy a memory pool. */ +tlsf_t tlsf_create(void* mem, size_t max_bytes); +tlsf_t tlsf_create_with_pool(void* mem, size_t pool_bytes, size_t max_bytes); +void tlsf_destroy(tlsf_t tlsf); +pool_t tlsf_get_pool(tlsf_t tlsf); + +/* Add/remove memory pools. */ +pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes); +void tlsf_remove_pool(tlsf_t tlsf, pool_t pool); + +/* malloc/memalign/realloc/free replacements. */ +void* tlsf_malloc(tlsf_t tlsf, size_t size); +void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size); +void* tlsf_memalign_offs(tlsf_t tlsf, size_t align, size_t size, size_t offset); +void* tlsf_malloc_addr(tlsf_t tlsf, size_t size, void *address); +void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size); +void tlsf_free(tlsf_t tlsf, void* ptr); + +/* Returns internal block size, not original request size */ +size_t tlsf_block_size(void* ptr); + +/* Overheads/limits of internal structures. */ +size_t tlsf_size(tlsf_t tlsf); +size_t tlsf_pool_overhead(void); +size_t tlsf_alloc_overhead(void); + +void tlsf_memory_info(tlsf_t tlsf, size_t *total, size_t *used, size_t *max_used); + +/** + * @brief Return the allocable size based on the size passed + * as parameter + * + * @param tlsf Pointer to the tlsf structure + * @param size The allocation size + * @return size_t The updated allocation size + */ +size_t tlsf_fit_size(tlsf_t tlsf, size_t size); + +/* Debugging. */ +typedef bool (*tlsf_walker)(void* ptr, size_t size, int used, void* user); +void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user); +/* Returns nonzero if any internal consistency check fails. */ +int tlsf_check(tlsf_t tlsf); +int tlsf_check_pool(pool_t pool); + +/** + * @brief Find the block containing the pointer passed as parameter + * + * @param pool The pool into which to look for the block + * @param ptr The pointer we want to find the containing block of + * @return void* The pointer to the containing block if found, NULL if not. + */ +void* tlsf_find_containing_block(pool_t pool, void *ptr); + +/** + * @brief Weak function called on every free block of memory allowing the user to implement + * application specific checks on the memory. + * + * @param start The start pointer to the memory of a block + * @param size The size of the memory in the block + * @param is_free Set to true when the memory belongs to a free block. + * False if it belongs to an allocated block. + * @return true The checks found no inconsistency in the memory + * @return false The checks in the function highlighted an inconsistency in the memory + */ +__attribute__((weak)) bool tlsf_check_hook(void *start, size_t size, bool is_free); + +#if defined(__cplusplus) +}; +#endif + +#endif diff --git a/test/externalModule/tlsf/tlsf_block_functions.h b/test/externalModule/tlsf/tlsf_block_functions.h new file mode 100644 index 0000000..8d44abc --- /dev/null +++ b/test/externalModule/tlsf/tlsf_block_functions.h @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2006-2016 Matthew Conte + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#if defined(__cplusplus) +extern "C" { +#endif + +/* +** Constants definition for poisoning. +** These defines are used as 3rd argument of tlsf_poison_fill_region() for readability purposes. +*/ +#define POISONING_AFTER_FREE true +#define POISONING_AFTER_MALLOC !POISONING_AFTER_FREE + +/* A type used for casting when doing pointer arithmetic. */ +typedef ptrdiff_t tlsfptr_t; + +/* +** Cast and min/max macros. +*/ +#if !defined(tlsf_cast) +#define tlsf_cast(t, exp) ((t)(exp)) +#endif +#if !defined(tlsf_min) +#define tlsf_min(a, b) ((a) < (b) ? (a) : (b)) +#endif +#if !defined(tlsf_max) +#define tlsf_max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +/* +** Set assert macro, if it has not been provided by the user. +*/ +#if !defined(tlsf_assert) +#define tlsf_assert RT_ASSERT +#endif + +typedef struct block_header_t +{ + /* Points to the previous physical block. */ + struct block_header_t *prev_phys_block; + + /* The size of this block, excluding the block header. */ + size_t size; + + /* Next and previous free blocks. */ + struct block_header_t *next_free; + struct block_header_t *prev_free; +} block_header_t; + +/* User data starts directly after the size field in a used block. */ +#define block_start_offset (offsetof(block_header_t, size) + sizeof(size_t)) + +/* +** A free block must be large enough to store its header minus the size of +** the prev_phys_block field, and no larger than the number of addressable +** bits for FL_INDEX. +*/ +#define block_size_min (sizeof(block_header_t) - sizeof(block_header_t *)) + +/* +** Since block sizes are always at least a multiple of 4, the two least +** significant bits of the size field are used to store the block status: +** - bit 0: whether block is busy or free +** - bit 1: whether previous block is busy or free +*/ +#define block_header_free_bit (1UL << 0) +#define block_header_prev_free_bit (1UL << 1) + +/* +** The size of the block header exposed to used blocks is the size field. +** The prev_phys_block field is stored *inside* the previous free block. +*/ +#define block_header_overhead (sizeof(size_t)) + +/* +** block_header_t member functions. +*/ +#define tlsf_decl rt_always_inline + +tlsf_decl size_t block_size(const block_header_t *block) +{ return block->size & ~(block_header_free_bit | block_header_prev_free_bit); } + +tlsf_decl void block_set_size(block_header_t *block, size_t size) +{ + const size_t oldsize = block->size; + block->size = size | (oldsize & (block_header_free_bit | block_header_prev_free_bit)); +} + +tlsf_decl int block_is_last(const block_header_t *block) { return block_size(block) == 0; } + +tlsf_decl int block_is_free(const block_header_t *block) +{ return tlsf_cast(int, block->size &block_header_free_bit); } + +tlsf_decl void block_set_free(block_header_t *block) { block->size |= block_header_free_bit; } + +tlsf_decl void block_set_used(block_header_t *block) { block->size &= ~block_header_free_bit; } + +tlsf_decl int block_is_prev_free(const block_header_t *block) +{ return tlsf_cast(int, block->size &block_header_prev_free_bit); } + +tlsf_decl void block_set_prev_free(block_header_t *block) { block->size |= block_header_prev_free_bit; } + +tlsf_decl void block_set_prev_used(block_header_t *block) { block->size &= ~block_header_prev_free_bit; } + +tlsf_decl block_header_t *block_from_ptr(const void *ptr) +{ return tlsf_cast(block_header_t *, tlsf_cast(unsigned char *, ptr) - block_start_offset); } + +tlsf_decl void *block_to_ptr(const block_header_t *block) +{ return tlsf_cast(void *, tlsf_cast(unsigned char *, block) + block_start_offset); } + +/* Return location of next block after block of given size. */ +tlsf_decl block_header_t *offset_to_block(const void *ptr, size_t size) +{ return tlsf_cast(block_header_t *, tlsf_cast(tlsfptr_t, ptr) + size); } + +/* Return location of previous block. */ +tlsf_decl block_header_t *block_prev(const block_header_t *block) +{ + tlsf_assert(block_is_prev_free(block) && "previous block must be free"); + return block->prev_phys_block; +} + +/* Return location of next existing block. */ +tlsf_decl block_header_t *block_next(const block_header_t *block) +{ + block_header_t *next = offset_to_block(block_to_ptr(block), block_size(block) - block_header_overhead); + tlsf_assert(!block_is_last(block)); + return next; +} + +/* Link a new block with its physical neighbor, return the neighbor. */ +tlsf_decl block_header_t *block_link_next(block_header_t *block) +{ + block_header_t *next = block_next(block); + next->prev_phys_block = block; + return next; +} + +tlsf_decl void block_mark_as_free(block_header_t *block) +{ + /* Link the block to the next block, first. */ + block_header_t *next = block_link_next(block); + block_set_prev_free(next); + block_set_free(block); +} + +tlsf_decl void block_mark_as_used(block_header_t *block) +{ + block_header_t *next = block_next(block); + block_set_prev_used(next); + block_set_used(block); +} + +#if defined(__cplusplus) +}; +#endif diff --git a/test/externalModule/tlsf/tlsf_control_functions.h b/test/externalModule/tlsf/tlsf_control_functions.h new file mode 100644 index 0000000..04d503a --- /dev/null +++ b/test/externalModule/tlsf/tlsf_control_functions.h @@ -0,0 +1,630 @@ +/* + * SPDX-FileCopyrightText: 2024 Matthew Conte + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once +#include "tlsf_block_functions.h" + +#if defined(__cplusplus) +extern "C" { +#define tlsf_decl static inline +#else +#define tlsf_decl rt_always_inline +#endif + +enum tlsf_config +{ + /* All allocation sizes and addresses are aligned to 4 bytes. */ + ALIGN_SIZE_LOG2 = 2, + ALIGN_SIZE = (1 << ALIGN_SIZE_LOG2), +}; + +typedef struct +{ + int32_t total; + int32_t used; + int32_t max_used; +} mem_record_t; + +/* The TLSF control structure. */ +typedef struct control_t +{ + /* Empty lists point at this block to indicate they are free. */ + block_header_t block_null; + + /* Local parameter for the pool. Given the maximum + * value of each field, all the following parameters + * can fit on 4 bytes when using bitfields + */ + unsigned int fl_index_count: 5; // 5 cumulated bits + unsigned int fl_index_shift: 3; // 8 cumulated bits + unsigned int fl_index_max: 6; // 14 cumulated bits + unsigned int sl_index_count: 6; // 20 cumulated bits + + /* log2 of number of linear subdivisions of block sizes. Larger + ** values require more memory in the control structure. Values of + ** 4 or 5 are typical. + */ + unsigned int sl_index_count_log2: 3; // 23 cumulated bits + unsigned int small_block_size: 8; // 31 cumulated bits + + /* size of the metadata ( size of control block, + * sl_bitmap and blocks ) + */ + size_t size; + + /* Bitmaps for free lists. */ + unsigned int fl_bitmap; + unsigned int *sl_bitmap; + + /* Head of free lists. */ + block_header_t **blocks; + mem_record_t mem_rec; +} control_t; + +/* +** Architecture-specific bit manipulation routines. +** +** TLSF achieves O(1) cost for malloc and free operations by limiting +** the search for a free block to a free list of guaranteed size +** adequate to fulfill the request, combined with efficient free list +** queries using bitmasks and architecture-specific bit-manipulation +** routines. +** +** Most modern processors provide instructions to count leading zeroes +** in a word, find the lowest and highest set bit, etc. These +** specific implementations will be used when available, falling back +** to a reasonably efficient generic implementation. +** +** NOTE: TLSF spec relies on ffs/fls returning value 0..31. +** ffs/fls return 1-32 by default, returning 0 for error. +*/ + +/* +** Detect whether or not we are building for a 32- or 64-bit (LP/LLP) +** architecture. There is no reliable portable method at compile-time. +*/ +#if defined(__alpha__) || defined(__ia64__) || defined(__x86_64__) || defined(_WIN64) || defined(__LP64__) || defined(__LLP64__) +#define TLSF_64BIT +#endif + +/* +** gcc 3.4 and above have builtin support, specialized for architecture. +** Some compilers masquerade as gcc; patchlevel test filters them out. +*/ +#if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) && defined(__GNUC_PATCHLEVEL__) + +#if defined(__SNC__) +/* SNC for Playstation 3. */ + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __builtin_clz(reverse); + return bit - 1; +} + +#else + +tlsf_decl int tlsf_ffs(unsigned int word) { return __builtin_ffs(word) - 1; } + +#endif + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __builtin_clz(word) : 0; + return bit - 1; +} + +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) && (defined(_M_IX86) || defined(_M_X64)) +/* Microsoft Visual C++ support on x86/X64 architectures. */ + +#include + +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) + +tlsf_decl int tlsf_fls(unsigned int word) +{ + unsigned long index; + return _BitScanReverse(&index, word) ? index : -1; +} + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + unsigned long index; + return _BitScanForward(&index, word) ? index : -1; +} + +#elif defined(_MSC_VER) && defined(_M_PPC) +/* Microsoft Visual C++ support on PowerPC architectures. */ + +#include + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = 32 - _CountLeadingZeros(word); + return bit - 1; +} + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - _CountLeadingZeros(reverse); + return bit - 1; +} + +#elif defined(__ARMCC_VERSION) +/* RealView Compilation Tools for ARM */ + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __clz(reverse); + return bit - 1; +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __clz(word) : 0; + return bit - 1; +} + +#elif defined(__ghs__) +/* Green Hills support for PowerPC */ + +#include + +tlsf_decl int tlsf_ffs(unsigned int word) +{ + const unsigned int reverse = word & (~word + 1); + const int bit = 32 - __CLZ32(reverse); + return bit - 1; +} + +tlsf_decl int tlsf_fls(unsigned int word) +{ + const int bit = word ? 32 - __CLZ32(word) : 0; + return bit - 1; +} + +#else +/* Fall back to generic implementation. */ + +tlsf_decl int tlsf_fls_generic(unsigned int word) +{ + int bit = 32; + + if (!word) { bit -= 1; } + if (!(word & 0xffff0000)) + { + word <<= 16; + bit -= 16; + } + if (!(word & 0xff000000)) + { + word <<= 8; + bit -= 8; + } + if (!(word & 0xf0000000)) + { + word <<= 4; + bit -= 4; + } + if (!(word & 0xc0000000)) + { + word <<= 2; + bit -= 2; + } + if (!(word & 0x80000000)) + { + word <<= 1; + bit -= 1; + } + + return bit; +} + +/* Implement ffs in terms of fls. */ +tlsf_decl int tlsf_ffs(unsigned int word) { return tlsf_fls_generic(word & (~word + 1)) - 1; } + +tlsf_decl int tlsf_fls(unsigned int word) { return tlsf_fls_generic(word) - 1; } + +#endif + +/* Possibly 64-bit version of tlsf_fls. */ +#if defined(TLSF_64BIT) +tlsf_decl int tlsf_fls_sizet(size_t size) +{ + int high = (int)(size >> 32); + int bits = 0; + if (high) { bits = 32 + tlsf_fls(high); } + else + { + bits = tlsf_fls((int)size & 0xffffffff); + } + return bits; +} +#else +#define tlsf_fls_sizet tlsf_fls +#endif + +tlsf_decl size_t align_up(size_t x, size_t align) +{ + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return (x + (align - 1)) & ~(align - 1); +} + +tlsf_decl size_t align_down(size_t x, size_t align) +{ + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return x - (x & (align - 1)); +} + +tlsf_decl void *align_ptr(const void *ptr, size_t align) +{ + const tlsfptr_t aligned = (tlsf_cast(tlsfptr_t, ptr) + (align - 1)) & ~(align - 1); + tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); + return tlsf_cast(void *, aligned); +} + +tlsf_decl size_t tlsf_align_size(void) { return ALIGN_SIZE; } + +tlsf_decl size_t tlsf_block_size_min(void) { return block_size_min; } + +tlsf_decl size_t tlsf_block_size_max(control_t *control) +{ + if (control == NULL) { return 0; } + return tlsf_cast(size_t, 1) << control->fl_index_max; +} + +/* +** Adjust an allocation size to be aligned to word size, and no smaller +** than internal minimum. +*/ +tlsf_decl size_t adjust_request_size(control_t *control, size_t size, size_t align) +{ + size_t adjust = 0; + if (size) + { + const size_t aligned = align_up(size, align); + + /* aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap */ + if (aligned < tlsf_block_size_max(control)) { adjust = tlsf_max(aligned, block_size_min); } + } + return adjust; +} + +/* +** TLSF utility functions. In most cases, these are direct translations of +** the documentation found in the white paper. +*/ + +tlsf_decl void mapping_insert(control_t *control, size_t size, int *fli, int *sli) +{ + int fl, sl; + if (size < control->small_block_size) + { + /* Store small blocks in first list. */ + fl = 0; + sl = tlsf_cast(int, size) / (control->small_block_size / control->sl_index_count); + } + else + { + fl = tlsf_fls_sizet(size); + sl = tlsf_cast(int, size >> (fl - control->sl_index_count_log2)) ^ (1 << control->sl_index_count_log2); + fl -= (control->fl_index_shift - 1); + } + *fli = fl; + *sli = sl; +} + +/* This version rounds up to the next block size (for allocations) */ +tlsf_decl void mapping_search(control_t *control, size_t *size, int *fli, int *sli) +{ + if (*size >= control->small_block_size) + { + const size_t round = (1 << (tlsf_fls_sizet(*size) - control->sl_index_count_log2)); + *size = align_up(*size, round); + } + mapping_insert(control, *size, fli, sli); +} + +tlsf_decl block_header_t *search_suitable_block(control_t *control, int *fli, int *sli) +{ + int fl = *fli; + int sl = *sli; + + /* + ** First, search for a block in the list associated with the given + ** fl/sl index. + */ + unsigned int sl_map = control->sl_bitmap[fl] & (~0U << sl); + if (!sl_map) + { + /* No block exists. Search in the next largest first-level list. */ + const unsigned int fl_map = control->fl_bitmap & (~0U << (fl + 1)); + if (!fl_map) + { + /* No free blocks available, memory has been exhausted. */ + return 0; + } + + fl = tlsf_ffs(fl_map); + *fli = fl; + sl_map = control->sl_bitmap[fl]; + } + tlsf_assert(sl_map && "internal error - second level bitmap is null"); + sl = tlsf_ffs(sl_map); + *sli = sl; + + /* Return the first block in the free list. */ + return control->blocks[fl * control->sl_index_count + sl]; +} + +/* Remove a free block from the free list.*/ +tlsf_decl void remove_free_block(control_t *control, block_header_t *block, int fl, int sl) +{ + block_header_t *prev = block->prev_free; + block_header_t *next = block->next_free; + tlsf_assert(prev && "prev_free field can not be null"); + tlsf_assert(next && "next_free field can not be null"); + next->prev_free = prev; + prev->next_free = next; + + /* If this block is the head of the free list, set new head. */ + if (control->blocks[fl * control->sl_index_count + sl] == block) + { + control->blocks[fl * control->sl_index_count + sl] = next; + + /* If the new head is null, clear the bitmap. */ + if (next == &control->block_null) + { + control->sl_bitmap[fl] &= ~(1U << sl); + + /* If the second bitmap is now empty, clear the fl bitmap. */ + if (!control->sl_bitmap[fl]) { control->fl_bitmap &= ~(1U << fl); } + } + } +} + +/* Insert a free block into the free block list. */ +tlsf_decl void insert_free_block(control_t *control, block_header_t *block, int fl, int sl) +{ + block_header_t *current = control->blocks[fl * control->sl_index_count + sl]; + tlsf_assert(current && "free list cannot have a null entry"); + tlsf_assert(block && "cannot insert a null entry into the free list"); + block->next_free = current; + block->prev_free = &control->block_null; + current->prev_free = block; + + tlsf_assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE) && "block not aligned properly"); + /* + ** Insert the new block at the head of the list, and mark the first- + ** and second-level bitmaps appropriately. + */ + control->blocks[fl * control->sl_index_count + sl] = block; + control->fl_bitmap |= (1U << fl); + control->sl_bitmap[fl] |= (1U << sl); +} + +/* Remove a given block from the free list. */ +tlsf_decl void block_remove(control_t *control, block_header_t *block) +{ + int fl, sl; + mapping_insert(control, block_size(block), &fl, &sl); + remove_free_block(control, block, fl, sl); +} + +/* Insert a given block into the free list. */ +tlsf_decl void block_insert(control_t *control, block_header_t *block) +{ + int fl, sl; + mapping_insert(control, block_size(block), &fl, &sl); + insert_free_block(control, block, fl, sl); +} + +tlsf_decl int block_can_split(block_header_t *block, size_t size) { return block_size(block) >= sizeof(block_header_t) + size; } + +/* Split a block into two, the second of which is free. */ +tlsf_decl block_header_t *block_split(block_header_t *block, size_t size) +{ + /* Calculate the amount of space left in the remaining block. + * REMINDER: remaining pointer's first field is `prev_phys_block` but this field is part of the + * previous physical block. */ + block_header_t *remaining = offset_to_block(block_to_ptr(block), size - block_header_overhead); + + /* `size` passed as an argument is the first block's new size, thus, the remaining block's size + * is `block_size(block) - size`. However, the block's data must be precedeed by the data size. + * This field is NOT part of the size, so it has to be substracted from the calculation. */ + const size_t remain_size = block_size(block) - (size + block_header_overhead); + + tlsf_assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE) && "remaining block not aligned properly"); + + tlsf_assert(block_size(block) == remain_size + size + block_header_overhead); + block_set_size(remaining, remain_size); + tlsf_assert(block_size(remaining) >= block_size_min && "block split with invalid size"); + + block_set_size(block, size); + block_mark_as_free(remaining); + + /** + * Here is the final outcome of this function: + * + * block remaining (block_ptr + size - BHO) + * + + + * | | + * v v + * +----------------------------------------------------------------------+ + * |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################| + * |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################| + * |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################| + * |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################| + * +----------------------------------------------------------------------+ + * | | | | + * + +<------------------------->+ +<-------------------------> + * BHO `size` (argument) bytes BHO `remain_size` bytes + * + * Where BHO = block_header_overhead, + * 0: part of the memory owned by a `block`'s previous neighbour, + * x: part of the memory owned by `block`. + * #: part of the memory owned by `remaining`. + */ + + return remaining; +} + +/*! + * @brief Weak function filling the given memory with a given fill pattern. + * + * @param start: pointer to the start of the memory region to fill + * @param size: size of the memory region to fill + * @param is_free: Indicate if the pattern to use the fill the region should be + * an after free or after allocation pattern. + */ +__attribute__((weak)) void block_absorb_post_hook(void *start, size_t size, bool is_free); + +/* Absorb a free block's storage into an adjacent previous free block. */ +tlsf_decl block_header_t *block_absorb(block_header_t *prev, block_header_t *block) +{ + tlsf_assert(!block_is_last(prev) && "previous block can't be last"); + /* Note: Leaves flags untouched. */ + prev->size += block_size(block) + block_header_overhead; + block_link_next(prev); + + if (block_absorb_post_hook != NULL) { block_absorb_post_hook(block, sizeof(block_header_t), POISONING_AFTER_FREE); } + + return prev; +} + +/* Merge a just-freed block with an adjacent previous free block. */ +tlsf_decl block_header_t *block_merge_prev(control_t *control, block_header_t *block) +{ + if (block_is_prev_free(block)) + { + block_header_t *prev = block_prev(block); + tlsf_assert(prev && "prev physical block can't be null"); + tlsf_assert(block_is_free(prev) && "prev block is not free though marked as such"); + block_remove(control, prev); + block = block_absorb(prev, block); + } + + return block; +} + +/* Merge a just-freed block with an adjacent free block. */ +tlsf_decl block_header_t *block_merge_next(control_t *control, block_header_t *block) +{ + block_header_t *next = block_next(block); + tlsf_assert(next && "next physical block can't be null"); + + if (block_is_free(next)) + { + tlsf_assert(!block_is_last(block) && "previous block can't be last"); + block_remove(control, next); + block = block_absorb(block, next); + } + + return block; +} + +/* Trim any trailing block space off the end of a block, return to pool. */ +tlsf_decl void block_trim_free(control_t *control, block_header_t *block, size_t size) +{ + tlsf_assert(block_is_free(block) && "block must be free"); + if (block_can_split(block, size)) + { + block_header_t *remaining_block = block_split(block, size); + block_link_next(block); + block_set_prev_free(remaining_block); + block_insert(control, remaining_block); + } +} + +/* Trim any trailing block space off the end of a used block, return to pool. */ +tlsf_decl void block_trim_used(control_t *control, block_header_t *block, size_t size) +{ + tlsf_assert(!block_is_free(block) && "block must be used"); + if (block_can_split(block, size)) + { + /* If the next block is free, we must coalesce. */ + block_header_t *remaining_block = block_split(block, size); + block_set_prev_used(remaining_block); + + remaining_block = block_merge_next(control, remaining_block); + block_insert(control, remaining_block); + } +} + +tlsf_decl block_header_t *block_trim_free_leading(control_t *control, block_header_t *block, size_t size) +{ + block_header_t *remaining_block = block; + if (block_can_split(block, size)) + { + /* We want to split `block` in two: the first block will be freed and the + * second block will be returned. */ + remaining_block = block_split(block, size - block_header_overhead); + + /* `remaining_block` is the second block, mark its predecessor (first + * block) as free. */ + block_set_prev_free(remaining_block); + + block_link_next(block); + + /* Put back the first block into the free memory list. */ + block_insert(control, block); + } + + return remaining_block; +} + +tlsf_decl block_header_t *block_locate_free(control_t *control, size_t *size) +{ + int fl = 0, sl = 0; + block_header_t *block = 0; + + if (*size) + { + mapping_search(control, size, &fl, &sl); + + /* + ** mapping_search can futz with the size, so for excessively large sizes it can sometimes wind up + ** with indices that are off the end of the block array. + ** So, we protect against that here, since this is the only callsite of mapping_search. + ** Note that we don't need to check sl, since it comes from a modulo operation that guarantees it's always in range. + */ + if (fl < control->fl_index_count) { block = search_suitable_block(control, &fl, &sl); } + } + + if (block) + { + tlsf_assert(block_size(block) >= *size); + remove_free_block(control, block, fl, sl); + } + + return block; +} + +tlsf_decl void *block_prepare_used(control_t *control, block_header_t *block, size_t size) +{ + void *p = 0; + if (block) + { + tlsf_assert(size && "size must be non-zero"); + block_trim_free(control, block, size); + block_mark_as_used(block); + p = block_to_ptr(block); + + control->mem_rec.used += (block_size(block) + tlsf_alloc_overhead()); + if (control->mem_rec.used > control->mem_rec.max_used) { control->mem_rec.max_used = control->mem_rec.used; } + } + return p; +} + +#undef tlsf_decl + +#if defined(__cplusplus) +}; +#endif diff --git a/test/valloc/valloc.c b/test/externalModule/valloc/valloc.c similarity index 65% rename from test/valloc/valloc.c rename to test/externalModule/valloc/valloc.c index 5852b64..2e54812 100644 --- a/test/valloc/valloc.c +++ b/test/externalModule/valloc/valloc.c @@ -5,17 +5,39 @@ #include #include "valloc.h" +#if defined(RyanJsonTestPlatformQemu) +#include "FreeRTOS.h" +#endif + #define HEADER_SIZE sizeof(int) -#define MALLOC_HEADER_SIZE 12 +#define MALLOC_HEADER_SIZE 0 static int count = 0; static int use = 0; +static void *vallocPlatformMalloc(size_t size) +{ +#if defined(RyanJsonTestPlatformQemu) + return pvPortMalloc(size); +#else + return malloc(size); +#endif +} + +static void vallocPlatformFree(void *ptr) +{ +#if defined(RyanJsonTestPlatformQemu) + vPortFree(ptr); +#else + free(ptr); +#endif +} + void *v_malloc(size_t size) { if (size == 0) { return NULL; } - void *p = malloc(size + HEADER_SIZE); + void *p = vallocPlatformMalloc(size + HEADER_SIZE); if (!p) { return NULL; } *(int *)p = (int)size; @@ -44,7 +66,7 @@ void v_free(void *block) count--; use -= size + MALLOC_HEADER_SIZE; - free(p); + vallocPlatformFree(p); } void *v_realloc(void *block, size_t size) @@ -64,8 +86,20 @@ void *v_realloc(void *block, size_t size) old_size = *(int *)raw; } - void *p = realloc(raw, size + HEADER_SIZE); + void *p = NULL; +#if defined(RyanJsonTestPlatformQemu) + p = vallocPlatformMalloc(size + HEADER_SIZE); + if (!p) { return NULL; } + if (block && old_size > 0) + { + size_t copySize = (size < (size_t)old_size) ? size : (size_t)old_size; + memcpy((char *)p + HEADER_SIZE, block, copySize); + vallocPlatformFree(raw); + } +#else + p = realloc(raw, size + HEADER_SIZE); if (!p) { return NULL; } +#endif *(int *)p = (int)size; diff --git a/test/valloc/valloc.h b/test/externalModule/valloc/valloc.h similarity index 93% rename from test/valloc/valloc.h rename to test/externalModule/valloc/valloc.h index 75c0c51..b44cc4f 100644 --- a/test/valloc/valloc.h +++ b/test/externalModule/valloc/valloc.h @@ -8,8 +8,8 @@ * \author Lamdonn * \details v1.0.0 ********************************************************************************************************/ -#ifndef __valloc_H -#define __valloc_H +#ifndef valloc +#define valloc #ifdef __cplusplus extern "C" { @@ -24,7 +24,6 @@ extern void v_free(void *block); extern void *v_realloc(void *block, size_t size); extern int v_mcheck(int *dstCount, int *dstUse); extern void displayMem(void); -extern void vallocInit(void); extern int32_t vallocGetArea(void); extern int32_t vallocGetUse(void); diff --git a/externalModule/yyjson/yyjson.c b/test/externalModule/yyjson/yyjson.c similarity index 100% rename from externalModule/yyjson/yyjson.c rename to test/externalModule/yyjson/yyjson.c diff --git a/externalModule/yyjson/yyjson.h b/test/externalModule/yyjson/yyjson.h similarity index 100% rename from externalModule/yyjson/yyjson.h rename to test/externalModule/yyjson/yyjson.h diff --git a/test/fuzzer/RyanJsonFuzzer.c b/test/fuzzer/RyanJsonFuzzer.c deleted file mode 100644 index 7132a38..0000000 --- a/test/fuzzer/RyanJsonFuzzer.c +++ /dev/null @@ -1,758 +0,0 @@ -#include "RyanJsonTest.h" -#include - -#define RyanJsonCheckGotoExit(EX) \ - RyanJsonCheckCode(EX, { \ - result = RyanJsonFalse; \ - goto __exit; \ - }) - -RyanJsonBool_e isEnableRandomMemFail = RyanJsonTrue; - -static RyanJsonBool_e RyanJsonFuzzerTestByParseAndPrint(RyanJson_t pJson, const char *data, uint32_t size) -{ - RyanJsonAssert(NULL == RyanJsonPrint(NULL, 100, RyanJsonFalse, NULL)); - RyanJsonAssert(NULL == RyanJsonPrintPreallocated(NULL, NULL, 100, RyanJsonFalse, NULL)); - RyanJsonAssert(NULL == RyanJsonPrintPreallocated(pJson, NULL, 100, RyanJsonFalse, NULL)); - RyanJsonAssert(NULL == RyanJsonPrintPreallocated(NULL, data, 100, RyanJsonFalse, NULL)); - RyanJsonAssert(NULL == RyanJsonPrintPreallocated(pJson, data, 0, RyanJsonFalse, NULL)); - - uint32_t len = 0; - char *jsonStr = - RyanJsonPrint(pJson, size % 5 ? 100 : 0, size % 2 ? RyanJsonFalse : RyanJsonTrue, &len); // 以带格式方式将数据打印出来 - RyanJsonCheckReturnFalse(NULL != jsonStr && len > 0); - RyanJsonFree(jsonStr); - - uint32_t bufLen = len * 3; - if (bufLen < size * 2) { bufLen = size * 2; } - if (bufLen < 4096) { bufLen = 4096; } - char *buf = malloc((size_t)bufLen); - { - uint32_t len2 = 0; - char *jsonStr2 = RyanJsonPrintPreallocated(pJson, buf, bufLen, size % 2 ? RyanJsonFalse : RyanJsonTrue, &len2); - // printf("len: %d, len2: %d, str: %s\r\n", len, len2, NULL == jsonStr2 ? "NULL" : jsonStr2); - RyanJsonCheckCode(NULL != jsonStr2 && len == len2, { - free(buf); - return RyanJsonFalse; - }); - } - - memcpy(buf, data, (size_t)size); - buf[size] = 0; - RyanJson_t jsonRoot = RyanJsonParse(buf); - RyanJsonCheckCode(NULL != jsonRoot, { - free(buf); - return RyanJsonFalse; - }); - - // 测试多次打印结果是否一致 - { - uint32_t len3 = 0; - char *jsonStr3 = RyanJsonPrint(jsonRoot, 100, size % 2 ? RyanJsonFalse : RyanJsonTrue, &len3); // 以带格式方式将数据打印出来 - RyanJsonCheckCode(NULL != jsonStr3 && len == len3, { - free(buf); - if (jsonStr3) { RyanJsonFree(jsonStr3); } - RyanJsonDelete(jsonRoot); - return RyanJsonFalse; - }); - - RyanJsonFree(jsonStr3); - } - - { - RyanJsonPrintPreallocated(jsonRoot, buf, bufLen / 15, RyanJsonTrue, NULL); - } - - free(buf); - RyanJsonDelete(jsonRoot); - return RyanJsonTrue; -} - -static RyanJsonBool_e RyanJsonFuzzerTestByDup(RyanJson_t pJson) -{ - RyanJsonBool_e result = RyanJsonTrue; - char *jsonStr = NULL; - char *jsonStrDup = NULL; - RyanJson_t pJsonDup = NULL; - - // 测试打印和复制功能 - uint32_t len = 0; - - jsonStr = RyanJsonPrint(pJson, 100, RyanJsonFalse, &len); - RyanJsonCheckGotoExit(NULL != jsonStr && len > 0); - - pJsonDup = RyanJsonDuplicate(pJson); - RyanJsonCheckGotoExit(NULL != pJsonDup); - - // 测试dup失败情况 - RyanJsonCheckGotoExit(NULL == RyanJsonDuplicate(NULL)); - - // 判断复制json的size是否一致 - RyanJsonCheckGotoExit(0 == RyanJsonGetSize(NULL)); - RyanJsonCheckGotoExit(RyanJsonGetSize(pJson) == RyanJsonGetSize(pJsonDup)); - RyanJsonCompare(pJson, pJsonDup); - RyanJsonCompareOnlyKey(pJson, pJsonDup); - // assert(RyanJsonTrue == RyanJsonCompare(pJson, pJsonDup)); // 大浮点数判断容易出错 - // RyanJsonCheckGotoExit(RyanJsonTrue == RyanJsonCompareOnlyKey(pJson, pJsonDup)); // 重复key也会失败 - - // 测试compare特殊情况 - RyanJsonCheckGotoExit(RyanJsonTrue == RyanJsonCompare(pJson, pJson)); - RyanJsonCheckGotoExit(RyanJsonFalse == RyanJsonCompare(NULL, pJsonDup)); - RyanJsonCheckGotoExit(RyanJsonFalse == RyanJsonCompare(pJson, NULL)); - RyanJsonCheckGotoExit(RyanJsonFalse == RyanJsonCompare(NULL, NULL)); - - // 测试compareKey特殊情况 - RyanJsonCheckGotoExit(RyanJsonTrue == RyanJsonCompareOnlyKey(pJson, pJson)); - RyanJsonCheckGotoExit(RyanJsonFalse == RyanJsonCompareOnlyKey(NULL, pJsonDup)); - RyanJsonCheckGotoExit(RyanJsonFalse == RyanJsonCompareOnlyKey(pJson, NULL)); - RyanJsonCheckGotoExit(RyanJsonFalse == RyanJsonCompareOnlyKey(NULL, NULL)); - - uint32_t dupLen = 0; - jsonStrDup = RyanJsonPrint(pJsonDup, 100, RyanJsonFalse, &dupLen); // 以带格式方式将数据打印出来 - RyanJsonCheckGotoExit(NULL != jsonStrDup && dupLen > 0); - - RyanJsonCheckCode(len == dupLen && 0 == memcmp(jsonStr, jsonStrDup, (size_t)len), { - printf("len:%" PRIu32 ", dupLen:%" PRIu32 "\r\n", len, dupLen); - printf("jsonStr:%s, jsonStrDup:%s\r\n", jsonStr, jsonStrDup); - RyanJsonCheckGotoExit(0); - }); - - if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) - { - // 测试size不相等 - RyanJsonDelete(RyanJsonDetachByIndex(pJson, 0)); - - if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) - { - // 改变key - RyanJson_t item; - RyanJsonObjectForEach(pJson, item) - { - if (RyanJsonIsKey(item)) - { - RyanJsonChangeKey(item, "key12231123"); - break; - } - } - - // 改变value - RyanJsonObjectForEach(pJson, item) - { - if (RyanJsonIsBool(item)) - { - RyanJsonChangeBoolValue(item, !RyanJsonGetBoolValue(item)); - break; - } - } - - // 改变obj的key - RyanJsonObjectForEach(pJson, item) - { - if (RyanJsonIsKey(item) && RyanJsonIsObject(item)) - { - RyanJsonChangeKey(item, "key12231123"); - break; - } - } - } - - RyanJsonCompare(pJson, pJsonDup); - RyanJsonCompareOnlyKey(pJson, pJsonDup); - } -__exit: - - if (jsonStr) - { - RyanJsonFree(jsonStr); - jsonStr = NULL; - } - if (pJsonDup) - { - RyanJsonDelete(pJsonDup); - pJsonDup = NULL; - } - if (jsonStrDup) - { - RyanJsonFree(jsonStrDup); - jsonStrDup = NULL; - } - - return result; -} - -static RyanJsonBool_e RyanJsonFuzzerTestByForEachChange(RyanJson_t pJson, uint32_t size) -{ - RyanJsonIsNull(pJson); - - if (RyanJsonIsKey(pJson)) - { - char *key = malloc(strlen(RyanJsonGetKey(pJson)) + 1); - if (key) - { - memcpy(key, RyanJsonGetKey(pJson), strlen(RyanJsonGetKey(pJson))); - key[strlen(RyanJsonGetKey(pJson))] = 0; - - RyanJsonChangeKey(pJson, "key"); - RyanJsonChangeKey(pJson, key); - free(key); - } - } - if (RyanJsonIsBool(pJson)) { RyanJsonChangeBoolValue(pJson, !RyanJsonGetBoolValue(pJson)); } - if (RyanJsonIsNumber(pJson)) - { - if (RyanJsonIsInt(pJson)) - { - int32_t value = RyanJsonGetIntValue(pJson); - RyanJsonChangeIntValue(pJson, (int32_t)size); - RyanJsonChangeIntValue(pJson, value); - } - if (RyanJsonIsDouble(pJson)) - { - double value = RyanJsonGetDoubleValue(pJson); - RyanJsonChangeDoubleValue(pJson, size * 1.123456789); - RyanJsonChangeDoubleValue(pJson, value); - } - } - - if (RyanJsonIsString(pJson)) - { - char *value = malloc(strlen(RyanJsonGetStringValue(pJson)) + 1); - if (value) - { - memcpy(value, RyanJsonGetStringValue(pJson), strlen(RyanJsonGetStringValue(pJson))); - value[strlen(RyanJsonGetStringValue(pJson))] = 0; - - RyanJsonChangeStringValue(pJson, "hello world"); - RyanJsonChangeStringValue(pJson, value); - - free(value); - } - } - - if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) - { - RyanJson_t item; - RyanJsonArrayForEach(pJson, item) - { RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonFuzzerTestByForEachChange(item, size)); } - } - - return RyanJsonTrue; -} - -static RyanJsonBool_e RyanJsonFuzzerTestByForEachGet2(RyanJson_t lastJson, RyanJson_t pJson, uint32_t index, uint32_t size) -{ - RyanJsonIsNull(pJson); - - RyanJsonAssert(NULL == RyanJsonGetValue(NULL)); - RyanJsonAssert(NULL == RyanJsonGetKey(NULL)); - RyanJsonAssert(NULL == RyanJsonGetStringValue(NULL)); - - RyanJsonAssert(NULL == RyanJsonGetObjectByKey(NULL, NULL)); - RyanJsonAssert(NULL == RyanJsonGetObjectByKey(pJson, NULL)); - RyanJsonAssert(NULL == RyanJsonGetObjectByKey(NULL, "NULL")); - if (!RyanJsonIsObject(pJson)) // pJson类型错误 - { - RyanJsonAssert(NULL == RyanJsonGetObjectByKey(pJson, "NULL")); - } - - RyanJsonAssert(NULL == RyanJsonGetObjectByIndex(NULL, 10)); - if (!RyanJsonIsArray(pJson) && !RyanJsonIsObject(pJson)) // pJson类型错误 - { - RyanJsonAssert(NULL == RyanJsonGetObjectByIndex(pJson, 0)); - } - - if (RyanJsonIsKey(pJson)) { RyanJsonGetObjectToKey(lastJson, RyanJsonGetKey(pJson)); } - else - { - RyanJsonGetObjectToIndex(lastJson, index); - } - - if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) - { - RyanJson_t item; - RyanJsonObjectForEach(pJson, item) { RyanJsonFuzzerTestByForEachGet2(pJson, item, index, size); } - } - - return RyanJsonTrue; -} - -static RyanJsonBool_e RyanJsonFuzzerTestByForEachGet(RyanJson_t pJson, uint32_t size) -{ - - if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) - { - RyanJson_t item; - uint32_t index = 0; - RyanJsonObjectForEach(pJson, item) - { - RyanJsonFuzzerTestByForEachGet2(pJson, item, index, size); - index++; - } - } - return RyanJsonTrue; -} - -static RyanJson_t RyanJsonFuzzerCreateRandomNode(RyanJson_t pJson) -{ - static int32_t count = 0; - static int32_t count2 = 0; - count++; - char *key = "true"; - if (count % 10 > 5) { key = NULL; } - switch (count % 50) - { - case 0: return RyanJsonCreateArray(); - case 1: return RyanJsonCreateObject(); - case 2: - count2++; - if (0 == count2 % 10) { return RyanJsonDuplicate(pJson); } - case 11: - case 12: - case 13: return RyanJsonCreateBool(key, RyanJsonTrue); - case 20: - case 21: - case 22: return RyanJsonCreateInt(key, count); - case 31: - case 32: - case 33: return RyanJsonCreateDouble(key, count * 1.123456789); - - default: return RyanJsonCreateString(key, "true"); - } -} - -static RyanJsonBool_e RyanJsonFuzzerTestByForEachCreate(RyanJson_t pJson, uint32_t size) -{ - // RyanJsonInsert的特殊情况 - RyanJsonCheckReturnFalse(RyanJsonFalse == RyanJsonInsert(NULL, UINT32_MAX, RyanJsonCreateString("key", "string"))); - RyanJsonCheckReturnFalse(RyanJsonFalse == RyanJsonInsert(pJson, UINT32_MAX, NULL)); - RyanJsonCheckReturnFalse(RyanJsonFalse == RyanJsonInsert(NULL, 0, NULL)); - - RyanJsonAssert(NULL == RyanJsonCreateString(NULL, NULL)); - RyanJsonAssert(NULL == RyanJsonCreateString("NULL", NULL)); - - RyanJsonAssert(RyanJsonFalse == RyanJsonChangeStringValue(NULL, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonChangeStringValue(pJson, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonChangeStringValue(NULL, "NULL")); - if (!RyanJsonIsKey(pJson) && !RyanJsonIsString(pJson)) // pJson类型错误 - { - RyanJsonAssert(RyanJsonFalse == RyanJsonChangeStringValue(pJson, "NULL")); - } - - RyanJsonAssert(RyanJsonFalse == RyanJsonChangeKey(NULL, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonChangeKey(pJson, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonChangeKey(NULL, "NULL")); - if (!RyanJsonIsKey(pJson) && !RyanJsonIsString(pJson)) // pJson类型错误 - { - RyanJsonAssert(RyanJsonFalse == RyanJsonChangeKey(pJson, "NULL")); - } - - RyanJsonAssert(RyanJsonFalse == RyanJsonAddItemToObject(NULL, NULL, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonAddItemToObject(pJson, NULL, NULL)); - - RyanJsonAssert(NULL == RyanJsonCreateIntArray(NULL, 0)); - RyanJsonAssert(NULL == RyanJsonCreateDoubleArray(NULL, 0)); - RyanJsonAssert(NULL == RyanJsonCreateStringArray(NULL, 0)); - - RyanJsonAssert(RyanJsonFalse == RyanJsonHasObjectToKey(NULL, "0", "1", "2", "3")); - RyanJsonAssert(RyanJsonFalse == RyanJsonHasObjectToIndex(NULL, 0, 1, 2, 3)); - RyanJsonAssert(RyanJsonFalse == RyanJsonHasObjectToKey(pJson, "0", "1", "2", "3")); - RyanJsonAssert(RyanJsonFalse == RyanJsonHasObjectToIndex(pJson, 0, 1, 2, 3)); - - char *key = "keyaaa"; - RyanJsonAddNullToObject(pJson, key); - if (RyanJsonIsKey(pJson)) { key = RyanJsonGetKey(pJson); } - if (RyanJsonIsBool(pJson)) { RyanJsonAddBoolToObject(pJson, key, RyanJsonGetBoolValue(pJson)); } - if (RyanJsonIsNumber(pJson)) - { - if (RyanJsonIsInt(pJson)) - { - RyanJsonAddIntToObject(pJson, key, RyanJsonGetIntValue(pJson)); - int arrayInt[] = {RyanJsonGetIntValue(pJson), RyanJsonGetIntValue(pJson), RyanJsonGetIntValue(pJson), - RyanJsonGetIntValue(pJson), RyanJsonGetIntValue(pJson)}; - RyanJsonAddItemToObject(pJson, (size % 2) ? key : "arrayString", - RyanJsonCreateIntArray(arrayInt, sizeof(arrayInt) / sizeof(arrayInt[0]))); - } - if (RyanJsonIsDouble(pJson)) - { - RyanJsonAddDoubleToObject(pJson, key, RyanJsonGetDoubleValue(pJson)); - double arrayDouble[] = {RyanJsonGetDoubleValue(pJson), RyanJsonGetDoubleValue(pJson), RyanJsonGetDoubleValue(pJson), - RyanJsonGetDoubleValue(pJson), RyanJsonGetDoubleValue(pJson)}; - RyanJsonAddItemToObject(pJson, (size % 2) ? key : "arrayString", - RyanJsonCreateDoubleArray(arrayDouble, sizeof(arrayDouble) / sizeof(arrayDouble[0]))); - } - } - - if (RyanJsonIsString(pJson)) - { - RyanJsonAddStringToObject(pJson, key, RyanJsonGetStringValue(pJson)); - const char *arrayString[] = {RyanJsonGetStringValue(pJson), RyanJsonGetStringValue(pJson), RyanJsonGetStringValue(pJson), - RyanJsonGetStringValue(pJson), RyanJsonGetStringValue(pJson)}; - RyanJsonAddItemToObject(pJson, (size % 2) ? key : "arrayString", - RyanJsonCreateStringArray(arrayString, sizeof(arrayString) / sizeof(arrayString[0]))); - } - - if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) - { - RyanJson_t item; - - RyanJsonObjectForEach(pJson, item) { RyanJsonFuzzerTestByForEachCreate(item, size); } - - RyanJson_t pJson2 = RyanJsonFuzzerCreateRandomNode(pJson); - RyanJsonAddItemToObject(pJson, key, pJson2); - - if (RyanJsonIsArray(pJson)) - { - RyanJsonAddNullToArray(pJson); - RyanJsonAddBoolToArray(pJson, size % 2 ? RyanJsonTrue : RyanJsonFalse); - RyanJsonAddItemToArray(pJson, RyanJsonFuzzerCreateRandomNode(RyanJsonGetArrayValue(pJson))); - } - } - - return RyanJsonTrue; -} - -/** - * @brief 测试 Json 的 Replace 功能(保护根节点) - * - * @param pJson 待测试的 Json 节点 - * @param size 用于计算 index 的模数 - * @param isFirst 是否为第一次调用(根节点) - * @return RyanJsonBool_e - */ -static RyanJsonBool_e RyanJsonFuzzerTestByForEachReplace(RyanJson_t pJson, uint32_t size) -{ - { - isEnableRandomMemFail = RyanJsonFalse; - RyanJson_t strItem = RyanJsonCreateString("", "NULL"); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(NULL, NULL, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(pJson, NULL, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(NULL, "NULL", NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(NULL, NULL, strItem)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(pJson, "NULL", NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(NULL, "NULL", strItem)); - if (!RyanJsonIsObject(pJson)) // pJson类型错误 - { - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(pJson, "NULL", strItem)); - } - - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByIndex(NULL, 0, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByIndex(pJson, 0, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByIndex(NULL, 0, strItem)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByIndex(pJson, 0, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByIndex(NULL, 0, strItem)); - if (!RyanJsonIsArray(pJson) && !RyanJsonIsObject(pJson)) // pJson类型错误 - { - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByIndex(pJson, 0, strItem)); - } - - RyanJson_t objItem = RyanJsonCreateObject(); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(objItem, "NULL", strItem)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByIndex(objItem, 0, strItem)); - - RyanJsonAddItemToObject(objItem, "item", RyanJsonCreateObject()); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByKey(objItem, "NULL222", strItem)); - RyanJsonAssert(RyanJsonFalse == RyanJsonReplaceByIndex(objItem, INT32_MAX, strItem)); - isEnableRandomMemFail = RyanJsonTrue; - - RyanJsonDelete(objItem); - RyanJsonDelete(strItem); - } - - // 只处理数组或对象 - if (!(RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson))) { return RyanJsonTrue; } - - // 递归替换子节点 - RyanJson_t item = NULL; - RyanJson_t LastItem = NULL; - RyanJsonObjectForEach(pJson, item) - { - if (RyanJsonTrue != RyanJsonFuzzerTestByForEachReplace(item, size)) { return RyanJsonFalse; } - LastItem = item; - } - - // 只有非根节点才做替换 - - // 按 key 替换(仅对象) - // 不要动第一个节点 - if (RyanJsonIsObject(pJson)) - { - if (LastItem && RyanJsonIsKey(LastItem)) - { - RyanJson_t newNode = RyanJsonFuzzerCreateRandomNode(pJson); - if (RyanJsonFalse == RyanJsonReplaceByKey(pJson, RyanJsonGetKey(LastItem), newNode)) - { - if (newNode) { RyanJsonDelete(newNode); } - return RyanJsonFalse; - } - } - } - - // 按 index 替换 - { - uint32_t idx = RyanJsonGetSize(pJson) % size; - RyanJson_t newNode = RyanJsonFuzzerCreateRandomNode(pJson); - if (RyanJsonFalse == RyanJsonReplaceByIndex(pJson, (size % 25) ? idx : 0, newNode)) - { - if (newNode) { RyanJsonDelete(newNode); } - return RyanJsonFalse; - } - } - - return RyanJsonTrue; -} - -/** - * @brief 测试 Json 的 Detach 分离功能(保护根节点) - * - * @param pJson 待测试的 Json 节点 - * @param size 用于计算 index 的模数 - * @param isFirst 是否为第一次调用(根节点) - * @return RyanJsonBool_e - */ -static RyanJsonBool_e RyanJsonFuzzerTestByForEachDetach(RyanJson_t pJson, uint32_t size) -{ - RyanJsonAssert(RyanJsonFalse == RyanJsonDetachByKey(NULL, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonDetachByKey(pJson, NULL)); - RyanJsonAssert(RyanJsonFalse == RyanJsonDetachByKey(NULL, "NULL")); - if (!RyanJsonIsObject(pJson)) // pJson类型错误 - { - RyanJsonAssert(NULL == RyanJsonDetachByKey(pJson, "NULL")); - } - - RyanJsonAssert(NULL == RyanJsonDetachByIndex(NULL, 10)); - if (!RyanJsonIsArray(pJson) && !RyanJsonIsObject(pJson)) // pJson类型错误 - { - RyanJsonAssert(NULL == RyanJsonDetachByIndex(pJson, 0)); - } - - if (!(RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson))) { return RyanJsonTrue; } - - // 递归遍历子节点 - RyanJson_t item = NULL; - RyanJson_t LastItem = NULL; - RyanJsonObjectForEach(pJson, item) - { - RyanJsonFuzzerTestByForEachDetach(item, size); - LastItem = item; - } - - // 只有非根节点才做 detach - - // 按 key 分离(仅对象) - if (RyanJsonIsObject(pJson)) - { - if (LastItem && RyanJsonIsKey(LastItem)) - { - RyanJson_t detached = RyanJsonDetachByKey(pJson, RyanJsonGetKey(LastItem)); - if (detached) { RyanJsonDelete(detached); } - - // RyanJsonAssert(RyanJsonFalse == RyanJsonDetachByKey(pJson, RyanJsonGetKey(LastItem))); - } - } - - // 按 index 分离 - { - uint32_t idx = RyanJsonGetSize(pJson) % size; - RyanJson_t detached = RyanJsonDetachByIndex(pJson, (size % 25) ? idx : 0); - if (detached) { RyanJsonDelete(detached); } - } - - return RyanJsonTrue; -} - -/** - * @brief 测试 Json 的 Delete 功能(保护根节点) - * - * @param pJson 待测试的 Json 节点 - * @param size 用于计算 index 的模数 - * @param isFirst 是否为第一次调用(根节点) - * @return RyanJsonBool_e - */ -static RyanJsonBool_e RyanJsonFuzzerTestByForEachDelete(RyanJson_t pJson, uint32_t size) -{ - if (!(RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson))) { return RyanJsonTrue; } - - // -------- 测试错误的 delete 调用 -------- - // Key 删除错误用例 - RyanJsonDeleteByKey(pJson, "non_exist_key"); - RyanJsonDeleteByKey(NULL, "some_key"); - RyanJsonDeleteByKey(pJson, NULL); - RyanJsonDeleteByKey(NULL, NULL); - - // Index 删除错误用例 - RyanJsonDeleteByIndex(pJson, RyanJsonGetSize(pJson)); // 越界 - RyanJsonDeleteByIndex(NULL, (RyanJsonGetSize(pJson) % size)); - RyanJsonDeleteByIndex(pJson, -size); // 负数 - RyanJsonDeleteByIndex(NULL, -size); - - // 递归遍历子节点 - RyanJson_t item = NULL; - RyanJson_t LastItem = NULL; - RyanJsonObjectForEach(pJson, item) - { - RyanJsonFuzzerTestByForEachDelete(item, size); - LastItem = item; - } - - // -------- 正常删除逻辑(保护根节点) -------- - - // 按 key 删除(仅对象) - if (RyanJsonIsObject(pJson)) - { - if (LastItem && RyanJsonIsKey(LastItem)) - { - - // printf("key is %d %s\r\n", RyanJsonGetType(LastItem), - // RyanJsonGetKey(LastItem) == NULL ? "NULL" : RyanJsonGetKey(LastItem)); - RyanJsonDeleteByKey(pJson, RyanJsonGetKey(LastItem)); - } - } - - // 按 index 删除 - uint32_t idx = RyanJsonGetSize(pJson) % size; - RyanJsonDeleteByIndex(pJson, (size % 25) ? idx : 0); - - return RyanJsonTrue; -} - -static RyanJsonBool_e RyanJsonFuzzerTestByMinify(const char *data, uint32_t size) -{ - char *buf = malloc(size + 100); - memcpy(buf, data, size); - memset(buf + size, 0, 100); - - uint32_t size2 = RyanJsonMinify(buf, (int32_t)size); - // 非法情况 - { - RyanJsonCheckReturnFalse(0 == RyanJsonMinify(NULL, 0)); - RyanJsonCheckReturnFalse(0 == RyanJsonMinify(NULL, 10)); - RyanJsonCheckReturnFalse(0 == RyanJsonMinify(NULL, -10)); - RyanJsonCheckReturnFalse(0 == RyanJsonMinify(buf, -10)); - } - - // 内存泄漏就是上面出错了 - RyanJson_t pJson2 = RyanJsonParseOptions(buf, size2, size % 2 ? RyanJsonTrue : RyanJsonFalse, NULL); - free(buf); - if (NULL != pJson2) - { - uint32_t len = 0; - char *jsonStr = RyanJsonPrint(pJson2, 100, RyanJsonFalse, &len); // 以带格式方式将数据打印出来 - RyanJsonCheckCode(NULL != jsonStr && len > 0, { - RyanJsonDelete(pJson2); - return RyanJsonFalse; - }); - RyanJsonFree(jsonStr); - RyanJsonDelete(pJson2); - } - else - { - return RyanJsonFalse; - } - - return RyanJsonTrue; -} - -static void *RyanJsonFuzzerMalloc(size_t size) -{ - static int32_t count = 0; - count++; - if (RyanJsonTrue == isEnableRandomMemFail) - { - if (0 == count % 598) { return NULL; } - } - return (char *)v_malloc(size); -} - -static void RyanJsonFuzzerFree(void *block) { v_free(block); } - -static void *RyanJsonFuzzerRealloc(void *block, size_t size) -{ - static int32_t count = 0; - count++; - if (RyanJsonTrue == isEnableRandomMemFail) - { - if (0 == count % 508) { return NULL; } - } - return (char *)v_realloc(block, size); -} - -int LLVMFuzzerTestOneInput(const char *data, uint32_t size) -{ - - // !检查分支覆盖率的时候要把这个取消掉,否则不知道是这个测试用例还是Fuzzer触发的,期望的是Fuzzer触发 - { - // // 执行基础测试 - // static bool isFirst = true; - // if (true == isFirst) - // { - // RyanJsonBool_e result = RyanJsonBaseTest(); - // if (RyanJsonTrue != result) - // { - // printf("%s:%d RyanJsonTest fail\r\n", __FILE__, __LINE__); - // return -1; - // } - - // RFC8259JsonTest(); - // isFirst = false; - // } - } - - // for (int i = 0; i < size; i++) { printf("%c", size, data[i]); } - // printf("\r\n"); - - RyanJsonInitHooks(NULL, RyanJsonFuzzerFree, RyanJsonFuzzerRealloc); - RyanJsonInitHooks(RyanJsonFuzzerMalloc, NULL, RyanJsonFuzzerRealloc); - RyanJsonInitHooks(RyanJsonFuzzerMalloc, RyanJsonFuzzerFree, NULL); - RyanJsonInitHooks(NULL, NULL, NULL); - - RyanJsonInitHooks(RyanJsonFuzzerMalloc, RyanJsonFuzzerFree, size % 2 ? NULL : RyanJsonFuzzerRealloc); - - RyanJsonAssert(NULL == RyanJsonParseOptions(NULL, 100, RyanJsonFalse, NULL)); - RyanJsonAssert(NULL == RyanJsonParseOptions(data, 0, RyanJsonFalse, NULL)); - - const char *parseEndPtr = NULL; - RyanJson_t pJson = RyanJsonParseOptions(data, size, size % 3 ? RyanJsonTrue : RyanJsonFalse, &parseEndPtr); - if (NULL != pJson) - { - assert(NULL != parseEndPtr && parseEndPtr - data <= size); - - { - isEnableRandomMemFail = RyanJsonFalse; - RyanJson_t pJson2 = RyanJsonDuplicate(pJson); - isEnableRandomMemFail = RyanJsonTrue; - RyanJsonCheckCode(RyanJsonFuzzerTestByForEachDelete(pJson2, size), { - RyanJsonDelete(pJson2); - goto __exit; - }); - RyanJsonDelete(pJson2); - } - - { - isEnableRandomMemFail = RyanJsonFalse; - RyanJson_t pJson2 = RyanJsonDuplicate(pJson); - isEnableRandomMemFail = RyanJsonTrue; - RyanJsonCheckCode(RyanJsonFuzzerTestByForEachDetach(pJson2, size), { - RyanJsonDelete(pJson2); - goto __exit; - }); - RyanJsonDelete(pJson2); - } - - RyanJsonFuzzerTestByMinify(data, size); - RyanJsonFuzzerTestByParseAndPrint(pJson, data, size); - RyanJsonFuzzerTestByForEachGet(pJson, size); - - RyanJsonFuzzerTestByDup(pJson); - RyanJsonCheckCode(RyanJsonFuzzerTestByForEachChange(pJson, size), { goto __exit; }); - RyanJsonCheckCode(RyanJsonFuzzerTestByForEachCreate(pJson, size), { goto __exit; }); - RyanJsonCheckCode(RyanJsonFuzzerTestByForEachReplace(pJson, size), { goto __exit; }); - - RyanJsonDelete(pJson); - } - - return 0; - -__exit: - RyanJsonDelete(pJson); - return 0; -} diff --git a/test/fuzzer/RyanJsonFuzzer.dict b/test/fuzzer/RyanJsonFuzzer.dict index e1c6f73..2fb4398 100644 --- a/test/fuzzer/RyanJsonFuzzer.dict +++ b/test/fuzzer/RyanJsonFuzzer.dict @@ -1,253 +1,312 @@ -# 基本关键字 +# ===================================================== +# RyanJson Fuzzer Dictionary +# 用于 libFuzzer 的 Json 模糊测试字典 +# ===================================================== + +# =================== +# 基本 Json 关键字 +# =================== "true" "false" "null" -# 对象结构 +# =================== +# Json 结构符号 +# =================== +# 对象 "{" "}" ":" "," -# 数组结构 +# 数组 "[" "]" -# 常见字符串模式 +# 字符串 +"\"" + +# =================== +# 常见键名 +# =================== "\"key\"" "\"value\"" "\"name\"" "\"id\"" -"\"string\"" -"\"number\"" -"\"message\"" "\"data\"" "\"status\"" "\"error\"" -""" -"\\" - -# 数字边界 +"\"message\"" +"\"type\"" +"\"result\"" +"\"code\"" +"\"items\"" +"\"count\"" +"\"total\"" +"\"list\"" + +# =================== +# 整数值 +# =================== "0" "1" "-1" -"1234567890" +"123" +"-123" +"2147483647" +"-2147483648" +"9223372036854775807" +"-9223372036854775808" +"999999999999999999999999999" +"-999999999999999999999999999" + +# =================== +# 浮点数值 +# =================== +"0.0" +"0.1" +"-0.1" "3.14159" +"3.141592653589793" +"1.7976931348623157e308" +"-1.7976931348623157e308" +"2.2250738585072014e-308" "1e10" +"1e-10" +"-1e10" "-1e-10" -"999999999999999999999999999" -"-999999999999999999999999999" -"-" -"000-000" -"-0045.12348" +"1E10" +"1E-10" +"1e+10" +"1.5e+9999" +"1e9999" +"-1e9999" +"123e100000" +"-123e100000" +"123e-10000000" +"123.456e-789" + +# =================== +# 特殊浮点数边界 +# =================== +"0.000001" +"0.0000001" +"999999.999999" +"1000000.0" +"0.123456789012345678901234567890" + +# =================== +# 科学计数法错误 +# =================== +"1e" +"1e+" +"1e-" +"1eE" +"1eAbc" +".1e10" +"1." +"1.e10" +"-.1" + +# =================== +# 前导零错误 +# =================== +"00" +"01" "0123" -"0123.123" - -# 嵌套结构 +"-00" +"-01" +"-0123" +"00.123" + +# =================== +# 简单结构 +# =================== +"{}" +"[]" "{\"a\":1}" +"{\"a\":\"b\"}" +"[1]" "[1,2,3]" +"[true,false,null]" + +# =================== +# 嵌套结构 +# =================== "{\"obj\":{\"nested\":true}}" -"{\"arr\":[{\"x\":1},{\"y\":2}]}" +"{\"arr\":[1,2,3]}" +"[{\"id\":1},{\"id\":2}]" "{\"deep\":{\"nest\":{\"more\":{\"inner\":{\"flag\":true}}}}}" -"[{\"id\":1,\"val\":true},{\"id\":2,\"val\":false}]" - -# 复杂对象 -"{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}" -"{\"items\":[{\"id\":1},{\"id\":2},{\"id\":3}]}" -"{\"user\":{\"id\":123,\"name\":\"Alice\",\"roles\":[\"admin\",\"editor\"]}}" -"{\"response\":{\"status\":200,\"data\":[{\"id\":1},{\"id\":2}]}}" +"[[[[[[1]]]]]]" +"{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":null}}}}}" + +# =================== +# 混合类型示例 +# =================== +"{\"int\":16,\"double\":16.89,\"string\":\"hello\",\"bool\":true,\"null\":null}" +"[16,16.89,\"hello\",true,false,null]" +"{\"mixed\":[1,\"two\",true,null,{\"nested\":\"obj\"}]}" + +# =================== +# 转义字符 +# =================== +"\\n" +"\\r" +"\\t" +"\\b" +"\\f" +"\\\"" +"\\\\" +"\\/" +"\\u0000" +"\\u0020" +"\\u00FF" +"\\uFFFF" + +# =================== +# Unicode 转义 +# =================== +"\\u4F60\\u597D" +"\\u4E16\\u754C" +"\\uD83D\\uDE00" +"\\uD83C\\uDF0D" + +# Unicode 代理对边界 +"\\uD800\\uDC00" +"\\uDBFF\\uDFFF" -# 边界结构 -# 键未加引号 +# 错误的 Unicode +"\\uD800" +"\\uDFFF" +"\\uZZZZ" +"\\u12" +"\\u123" +"\\uD800\\u0041" + +# =================== +# 特殊字符串 +# =================== +"{\"empty\":\"\"}" +"{\"space\":\" \"}" +"{\"tab\":\"\\t\"}" +"{\"newline\":\"\\n\"}" +"{\"unicode\":\"\\u4E2D\\u6587\"}" +"{\"emoji\":\"\\uD83D\\uDE00\"}" + +# =================== +# RESTful 风格响应 +# =================== +"{\"status\":200,\"message\":\"OK\"}" +"{\"status\":404,\"error\":\"Not Found\"}" +"{\"status\":500,\"error\":\"Internal Server Error\"}" +"{\"code\":0,\"data\":null}" +"{\"success\":true,\"data\":[]}" +"{\"success\":false,\"message\":\"Error\"}" + +# =================== +# 分页响应 +# =================== +"{\"page\":1,\"pageSize\":10,\"total\":100,\"data\":[]}" +"{\"offset\":0,\"limit\":20,\"items\":[]}" + +# =================== +# 重复键(测试处理) +# =================== +"{\"key\":1,\"key\":2}" +"{\"a\":\"first\",\"a\":\"second\"}" +"{\"dup\":true,\"dup\":false}" + +# =================== +# 语法错误示例 +# =================== +# 缺少引号 "{a:1}" +"{key:\"value\"}" # 缺少冒号 "{\"a\" 1}" # 缺少逗号 "{\"a\":1 \"b\":2}" +"[1 2 3]" # 多余逗号 "{\"a\":1,}" - -# 数组尾逗号 "[1,2,3,]" - -# 空对象错误 "{,}" - -# 空数组错误 "[,]" +"[1,,2]" -# 未闭合字符串 -"{\"a\":\"value}" - -# 单引号字符串 -"{\"a\":'value'}" - -# 非法转义 -"{\"a\":\"\\q\"}" +# 未闭合 +"{\"a\":1" +"[1,2,3" +"{\"str\":\"unclosed" +"[" +"{" -# 非法 Unicode 转义 -"{\"a\":\"\\uZZZZ\"}" +# 单引号(非法) +"{'a':'b'}" -# 位数不足 -"{\"a\":\"\\u12\"}" +# 注释(非法) +"// comment" +"/* block */" +"{\"a\":1}// trailing" +# =================== # 控制字符 -"{\"a\":\"\\x07\"}" - -# 小数点后无数字 -"1." - -# 科学计数法错误 -"1e" - -# 科学计数法错误 -"1e+" - -# 超大指数 -"1e9999" +# =================== +"\x00" +"\x01" +"\x1F" +"\x7F" + +# =================== +# 非法字节序列 +# =================== +"\xC0\xAF" +"\xFF\xFF" +"\xFE\xFF" +"\x80" +"\xBF" -# 拼写错误 +# =================== +# 边缘 token +# =================== "True" "False" "Null" +"TRUE" +"FALSE" +"NULL" "tru" +"fals" "nul" -# 顶层错误 -"string" -"123" -"true" -"null" - -# 注释错误:单行 -"// comment" -"/* block comment */" - -# 非 UTF-8 字节流 -"\xC0\xAF" -"\xFF\xFF" -"\xFE\xFF" - -# 空结构 -"{}" -"[]" - -# 特殊转义 -"\\n" -"\\r" -"\\t" -"\\b" -"\\f" -"\\u0000" -"\\uD800\\uDC00" -"\\uDBFF\\uDFFF" -"\\\"" -"\\\\" -"/" - -# 一维对象,覆盖所有 JSON 类型 -"{\"string\":\"hello\",\"number\":123,\"boolean_true\":true,\"boolean_false\":false,\"null_value\":null,\"array\":[1,\"two\",false,null],\"object\":{\"nested_key\":\"nested_value\"}}" - -# 错误重复结构 -"[{\"error\":4,\"error\":\"45\"}]" -"[{\"id\":1,\"id\":2}]" -"[{\"name\":\"Alice\",\"name\":\"Bob\"}]" -"[{\"value\":true,\"value\":false}]" -"[{\"unicode\":\"\\u4F60\\u597D\",\"unicode\":\"\\u4E16\\u754C\"}]" -"[{\"data\":[1,2,3],\"data\":{\"x\":1}}]" -"[{\"nested\":{\"a\":1},\"nested\":{\"a\":2}}]" -"[{\"error\":null,\"error\":999}]" -"[{\"flag\":false,\"flag\":true}]" -"[{\"number\":123,\"number\":\"123\"}]" -"[{\"list\":[1,2],\"list\":[3,4]}]" - -# Unicode 示例(转义形式) -"{\"unicode\":\"\\u4F60\\u597D\"}" -"{\"unicode\":\"\\u4E16\\u754C\"}" -"{\"unicode\":\"\\uD83C\\uDF0D\"}" -"{\"unicode\":\"\\uD83D\\uDE00\"}" -# 孤立高代理 -"{\"unicode\":\"\\uD800\"}" -# 孤立低代理 -"{\"unicode\":\"\\uDFFF\"}" -# 超出最大码点 -"{\"unicode\":\"\\u110000\"}" - # 非十六进制 -"{\"unicode\":\"\\uZZZZ\"}" -# 位数不足 -"{\"unicode\":\"\\u12\"}" -# 错误代理配对 -"{\"unicode\":\"\\uD800\\u0041\"}" - -# RESTful 风格常见响应 -"{\"status\":200,\"message\":\"OK\"}" -"{\"status\":404,\"error\":\"Not Found\"}" -"{\"status\":500,\"error\":\"Internal Server Error\"}" -"{\"status\":401,\"error\":\"Unauthorized\"}" -"{\"status\":403,\"error\":\"Forbidden\"}" - -# RESTful 数据响应 -"{\"status\":200,\"data\":{\"id\":1,\"name\":\"Alice\"}}" -"{\"status\":200,\"data\":[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]}" -"{\"status\":200,\"data\":{\"items\":[{\"id\":1,\"value\":true},{\"id\":2,\"value\":false}]}}" - -# RESTful 分页响应 -"{\"status\":200,\"page\":1,\"pageSize\":10,\"total\":100,\"data\":[{\"id\":1},{\"id\":2}]}" -"{\"status\":200,\"meta\":{\"page\":2,\"limit\":20},\"data\":[{\"id\":21},{\"id\":22}]}" - -# RESTful 错误响应 -"{\"error\":{\"code\":1234,\"message\":\"Invalid request\"}}" -"{\"error\":{\"code\":5678,\"message\":\"Timeout\"}}" - -# RESTful 响应 -"{\"status\":200,\"user\":{\"id\":123,\"name\":\"Alice\",\"roles\":[\"admin\",\"editor\"]},\"token\":\"abcdef123456\"}" -"{\"status\":200,\"config\":{\"theme\":\"dark\",\"language\":\"en\"},\"features\":[\"chat\",\"upload\",\"search\"]}" - -# 循环/深度嵌套场景 -"{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":{\"f\":{\"g\":true}}}}}}}" -"[[[[[[[[[[1]]]]]]]]]]" -"[{\"a\":[{\"b\":[{\"c\":[{\"d\":true}]}]}]}]" -"{\"a\":[{\"b\":[{\"c\":[{\"d\":[{\"e\":false}]}]}]}]}" -"{\"node\":{\"id\":1,\"child\":{\"id\":2,\"child\":{\"id\":3,\"child\":{\"id\":4}}}}}" -"{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":null}}}}}}" -"{\"status\":200,\"data\":{\"items\":[{\"id\":1,\"children\":[{\"id\":2,\"children\":[{\"id\":3}]}]}]}}" -"{\"mixed\":[{\"obj\":{\"arr\":[{\"obj\":{\"arr\":[{\"obj\":{\"arr\":[true]}]}]}]}}]}" - -# 极端情况 -"{\"long\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}" -"{\"big\":999999999999999999999999999}" -"[[],[],[],[],[],[],[],[],[],[]]" -"{\"nestedArray\":[[1,2],[3,4],[5,[6,[7,[8]]]]]}" -"{\"escape\":\"line1\\nline2\\tTabbed\"}" -"{\"mixed\":[16,16.89,\"hello\",true,false,null,{\"deep\":{}}]}" - -# 非法/边界片段(测试错误处理) -",{}" -":[]" -"[[]]" -"{{}}" -",[]" -":{}" -"''" -"\\x00" -"\\0" - -"//" -"/**/" - -"\x01\x00" +# =================== +# 极端嵌套 +# =================== +"[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]" +"{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{}}}}}}}}}}}" + +# =================== +# 超长字符串 +# =================== +"{\"long\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}" + +# =================== +# 空白字符变体 +# =================== +" { } " +"\x09{\x09}\x09" +"\x0a[\x0a]\x0a" +"\x0d\x0a{\x0d\x0a}\x0d\x0a" +"{ \"a\" : 1 }" +"[ 1 , 2 , 3 ]" + +# =================== +# 二进制边界测试 +# =================== "\x00\x00\x00\x00" -"\x00\x00\x00\x00\x00\x00\x00\x00" -"\x00\x00\x00\x00\x00\x00\x00\x01" -"\x01\x00\x00\x00\x00\x00\x00\x00" -"\x10\x00\x00\x00\x00\x00\x00\x00" - -"\xff\xff" -"\xfe\xff\xff\xee" -"\xff\xff\xff\xff" -"\xfe\xff\xff\xff\xff\xff\xff\xfa" -"\xfb\xff\xff\xff\xff\xff\xff\xff" -"\xff\xff\xff\xff\xff\xff\xff\xff" \ No newline at end of file +"\xFF\xFF\xFF\xFF" +"\x01\x00\x00\x00" +"\x00\x00\x00\x01" \ No newline at end of file diff --git a/test/fuzzer/cases/fuzzerCreate.c b/test/fuzzer/cases/fuzzerCreate.c new file mode 100644 index 0000000..670868d --- /dev/null +++ b/test/fuzzer/cases/fuzzerCreate.c @@ -0,0 +1,483 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 创建与插入测试 + * + * 测试 RyanJson 的节点创建、数据类型设置以及对象/数组的插入操作。 + * 覆盖场景: + * 异常参数注入:测试空指针、无效 key、类型不匹配等错误处理。 + * 基础类型创建:测试 Bool/Int/Double/String 及其数组的创建与添加。 + * 递归结构构建:递归地向对象/数组中添加随机生成的子节点。 + * 边界插入测试:覆盖数组头部与中间位置插入。 + * + * @param state Fuzzer 状态上下文 + * @param pJson 当前正在操作的 Json 节点 + * @param size 输入数据大小,用于控制递归深度和随机决策 + */ +RyanJsonBool_e RyanJsonFuzzerTestCreate(RyanJson_t pJson, uint32_t size) +{ + // 覆盖 RyanJsonIsDetachedItem 的防御分支: + // next==NULL 但 IsLast==1 属于非法游离态,应返回 false。 + // 该路径只需覆盖一次,避免每轮 fuzz 重复创建节点。 + static RyanJsonBool_e detachedFlagBranchCovered = RyanJsonFalse; + if (RyanJsonFalse == detachedFlagBranchCovered) + { + RyanJsonBool_e lastIsEnableMemFail = g_fuzzerState.isEnableMemFail; + g_fuzzerState.isEnableMemFail = false; + + RyanJson_t malformedDetached = RyanJsonCreateInt(NULL, 1); + assert(NULL != malformedDetached); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(malformedDetached)); + RyanJsonSetPayloadIsLastByFlag(malformedDetached, RyanJsonTrue); + assert(RyanJsonFalse == RyanJsonIsDetachedItem(malformedDetached)); + // 恢复 flag,避免后续 Delete 走错链表语义 + RyanJsonSetPayloadIsLastByFlag(malformedDetached, RyanJsonFalse); + RyanJsonDelete(malformedDetached); + + g_fuzzerState.isEnableMemFail = lastIsEnableMemFail; + detachedFlagBranchCovered = RyanJsonTrue; + } + + // 覆盖对象重复 key 防御分支(一次性) + static RyanJsonBool_e duplicateKeyGuardCovered = RyanJsonFalse; + if (RyanJsonFalse == duplicateKeyGuardCovered) + { + RyanJsonBool_e lastIsEnableMemFail = g_fuzzerState.isEnableMemFail; + g_fuzzerState.isEnableMemFail = false; + + RyanJson_t obj = RyanJsonCreateObject(); + assert(NULL != obj); + assert(RyanJsonTrue == RyanJsonAddIntToObject(obj, "dup", 1)); +#if true == RyanJsonStrictObjectKeyCheck + assert(RyanJsonFalse == RyanJsonAddIntToObject(obj, "dup", 2)); + assert(RyanJsonFalse == RyanJsonInsert(obj, UINT32_MAX, RyanJsonCreateInt("dup", 3))); + assert(1 == RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "dup"))); +#else + assert(RyanJsonTrue == RyanJsonAddIntToObject(obj, "dup", 2)); + assert(RyanJsonTrue == RyanJsonInsert(obj, UINT32_MAX, RyanJsonCreateInt("dup", 3))); + assert(3 == RyanJsonGetSize(obj)); +#if true == RyanJsonDefaultAddAtHead + assert(2 == RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "dup"))); +#else + assert(1 == RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "dup"))); +#endif +#endif + RyanJsonDelete(obj); + + g_fuzzerState.isEnableMemFail = lastIsEnableMemFail; + duplicateKeyGuardCovered = RyanJsonTrue; + } + + // RyanJsonInsert 特殊路径:模拟内存分配失败或无效参数输入 + uint32_t index = 6; + + if (RyanJsonFuzzerShouldFail(100)) + { + assert(RyanJsonFalse == RyanJsonInsert(NULL, UINT32_MAX, RyanJsonCreateString("key", "string"))); + assert(RyanJsonFalse == RyanJsonInsert(pJson, UINT32_MAX, NULL)); + assert(RyanJsonFalse == RyanJsonInsert(NULL, 0, NULL)); + + assert(NULL == RyanJsonCreateString(NULL, NULL)); + assert(NULL == RyanJsonCreateString("NULL", NULL)); + + assert(RyanJsonFalse == RyanJsonChangeStringValue(NULL, NULL)); + assert(RyanJsonFalse == RyanJsonChangeStringValue(pJson, NULL)); + assert(RyanJsonFalse == RyanJsonChangeStringValue(NULL, "NULL")); + if (RyanJsonFalse == RyanJsonIsKey(pJson) && RyanJsonFalse == RyanJsonIsString(pJson)) // pJson 类型错误 + { + assert(RyanJsonFalse == RyanJsonChangeStringValue(pJson, "NULL")); + } + + assert(RyanJsonFalse == RyanJsonChangeKey(NULL, NULL)); + assert(RyanJsonFalse == RyanJsonChangeKey(pJson, NULL)); + assert(RyanJsonFalse == RyanJsonChangeKey(NULL, "NULL")); + if (RyanJsonFalse == RyanJsonIsKey(pJson) && RyanJsonFalse == RyanJsonIsString(pJson)) // pJson 类型错误 + { + g_fuzzerState.isEnableMemFail = false; + assert(RyanJsonFalse == RyanJsonChangeKey(pJson, "NULL")); + g_fuzzerState.isEnableMemFail = true; + } + + // 测试“无 key 但有 strValue”的分支 + if (RyanJsonFalse == RyanJsonIsKey(pJson) && RyanJsonTrue == RyanJsonIsString(pJson)) + { + g_fuzzerState.isEnableMemFail = false; + assert(RyanJsonFalse == RyanJsonChangeKey(pJson, "NULL")); + g_fuzzerState.isEnableMemFail = true; + } + + assert(RyanJsonFalse == RyanJsonChangeIntValue(NULL, 0)); + assert(RyanJsonFalse == RyanJsonChangeDoubleValue(NULL, 0)); + assert(RyanJsonFalse == RyanJsonChangeBoolValue(NULL, 0)); + + // Change 接口类型不匹配分支 + RyanJson_t mismatchNode = RyanJsonCreateDouble(NULL, 1.0); + assert(RyanJsonFalse == RyanJsonChangeIntValue(mismatchNode, 7)); + if (NULL != mismatchNode) { RyanJsonDelete(mismatchNode); } + + mismatchNode = RyanJsonCreateInt(NULL, 1); + assert(RyanJsonFalse == RyanJsonChangeDoubleValue(mismatchNode, 7.0)); + if (NULL != mismatchNode) { RyanJsonDelete(mismatchNode); } + + mismatchNode = RyanJsonCreateInt(NULL, 1); + assert(RyanJsonFalse == RyanJsonChangeBoolValue(mismatchNode, RyanJsonTrue)); + if (NULL != mismatchNode) { RyanJsonDelete(mismatchNode); } + + mismatchNode = RyanJsonCreateString(NULL, "v"); + assert(RyanJsonFalse == RyanJsonChangeKey(mismatchNode, "k2")); + if (NULL != mismatchNode) { RyanJsonDelete(mismatchNode); } + + mismatchNode = RyanJsonCreateBool(NULL, RyanJsonTrue); + assert(RyanJsonFalse == RyanJsonChangeStringValue(mismatchNode, "x")); + if (NULL != mismatchNode) { RyanJsonDelete(mismatchNode); } + + assert(RyanJsonFalse == RyanJsonAddItemToObject(NULL, NULL, NULL)); + assert(RyanJsonFalse == RyanJsonAddItemToObject(pJson, NULL, NULL)); + + assert(NULL == RyanJsonCreateIntArray(NULL, 0)); + assert(NULL == RyanJsonCreateDoubleArray(NULL, 0)); + assert(NULL == RyanJsonCreateStringArray(NULL, 0)); + + // 仅调用,不判断返回值,因为有可能会成功的 + RyanJsonHasObjectToKey(NULL, "0", "1", "2", "3"); + RyanJsonHasObjectToIndex(NULL, 0, 1, 2, 3); + RyanJsonHasObjectToKey(pJson, "0", "1", "2", "3"); + RyanJsonHasObjectToIndex(pJson, 0, 1, 2, 3); + RyanJsonHasObjectToIndex(pJson, 0); + + // 已挂树的 item 不应再次 Insert/AddItem + { + g_fuzzerState.isEnableMemFail = false; + + RyanJson_t arr1 = RyanJsonCreateArray(); + RyanJson_t arr2 = RyanJsonCreateArray(); + RyanJson_t attachedArr = RyanJsonCreateArray(); + assert(RyanJsonFalse == RyanJsonIsDetachedItem(NULL)); + assert(NULL != arr1 && NULL != arr2 && NULL != attachedArr); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(attachedArr)); + assert(RyanJsonTrue == RyanJsonInsert(arr1, 0, attachedArr)); + assert(RyanJsonFalse == RyanJsonIsDetachedItem(attachedArr)); + assert(RyanJsonFalse == RyanJsonInsert(arr2, 0, attachedArr)); + assert(RyanJsonFalse == RyanJsonAddItemToArray(arr2, attachedArr)); + RyanJson_t detachedArr = RyanJsonDetachByIndex(arr1, 0); + assert(detachedArr == attachedArr); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(detachedArr)); + RyanJsonDelete(detachedArr); + + RyanJson_t obj1 = RyanJsonCreateObject(); + RyanJson_t obj2 = RyanJsonCreateObject(); + RyanJson_t attachedObj = RyanJsonInternalCreateObjectAndKey("k"); + assert(NULL != obj1 && NULL != obj2 && NULL != attachedObj); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(attachedObj)); + assert(RyanJsonTrue == RyanJsonInsert(obj1, 0, attachedObj)); + assert(RyanJsonFalse == RyanJsonIsDetachedItem(attachedObj)); + assert(RyanJsonFalse == RyanJsonInsert(obj2, 0, attachedObj)); + assert(RyanJsonFalse == RyanJsonAddItemToObject(obj2, "dup", attachedObj)); + RyanJson_t detachedObj = RyanJsonDetachByIndex(obj1, 0); + assert(detachedObj == attachedObj); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(detachedObj)); + RyanJsonDelete(detachedObj); + + RyanJsonDelete(arr2); + RyanJsonDelete(obj2); + RyanJsonDelete(arr1); + RyanJsonDelete(obj1); + + g_fuzzerState.isEnableMemFail = true; + } + } + + char *key = "keyaaa"; + RyanJsonAddNullToObject(pJson, key); + + // 如果当前节点是 key 类型,尝试获取其 key 字符串作为后续操作 key + if (RyanJsonTrue == RyanJsonIsKey(pJson)) + { + // Change 系列测试已在 modify 用例覆盖 + key = RyanJsonGetKey(pJson); + } + + // 标量类型追加测试 + if (RyanJsonTrue == RyanJsonIsBool(pJson) && RyanJsonFuzzerShouldFail(index)) + { + if (RyanJsonTrue == RyanJsonAddBoolToObject(pJson, key, RyanJsonGetBoolValue(pJson))) + { + fuzzTestWithMemFail( + assert(RyanJsonGetBoolValue(RyanJsonGetObjectByKey(pJson, key)) == RyanJsonGetBoolValue(pJson))); + } + } + + if (RyanJsonTrue == RyanJsonIsNumber(pJson) && RyanJsonFuzzerShouldFail(index)) + { + if (RyanJsonTrue == RyanJsonIsInt(pJson)) + { + if (RyanJsonTrue == RyanJsonAddIntToObject(pJson, key, RyanJsonGetIntValue(pJson))) + { + fuzzTestWithMemFail( + assert(RyanJsonGetIntValue(RyanJsonGetObjectByKey(pJson, key)) == RyanJsonGetIntValue(pJson))); + } + + // 构造测试用 Int 数组 + int32_t val = RyanJsonGetIntValue(pJson); + int32_t testIntArray[] = {val, val, val, val, val}; + RyanJsonBool_e jsonAddResult = RyanJsonAddItemToObject( + pJson, (0 != size % 2) ? key : "arrayString", + RyanJsonCreateIntArray(testIntArray, sizeof(testIntArray) / sizeof(testIntArray[0]))); + if (RyanJsonTrue == jsonAddResult) + { + fuzzTestWithMemFail({ + RyanJson_t itemIntArr = RyanJsonGetObjectByKey(pJson, key); + assert(RyanJsonIsArray(itemIntArr)); + assert(RyanJsonGetArraySize(itemIntArr) == sizeof(testIntArray) / sizeof(testIntArray[0])); + RyanJson_t item; + RyanJsonArrayForEach(itemIntArr, item) + { + assert(RyanJsonGetIntValue(item) == val); + } + }); + } + } + else if (RyanJsonTrue == RyanJsonIsDouble(pJson)) + { + if (RyanJsonTrue == RyanJsonAddDoubleToObject(pJson, key, RyanJsonGetDoubleValue(pJson))) + { + fuzzTestWithMemFail(assert(RyanJsonCompareDouble(RyanJsonGetDoubleValue(RyanJsonGetObjectByKey(pJson, key)), + RyanJsonGetDoubleValue(pJson)))); + } + + // 构造测试用 Double 数组 + + double val = RyanJsonGetDoubleValue(pJson); + double testDoubleArray[] = {val, val, val, val, val}; + RyanJsonBool_e jsonAddResult = RyanJsonAddItemToObject( + pJson, (0 != size % 2) ? key : "arrayString", + RyanJsonCreateDoubleArray(testDoubleArray, sizeof(testDoubleArray) / sizeof(testDoubleArray[0]))); + if (RyanJsonTrue == jsonAddResult) + { + fuzzTestWithMemFail({ + RyanJson_t itemIntArr = RyanJsonGetObjectByKey(pJson, key); + assert(RyanJsonIsArray(itemIntArr)); + assert(RyanJsonGetArraySize(itemIntArr) == sizeof(testDoubleArray) / sizeof(testDoubleArray[0])); + RyanJson_t item; + RyanJsonArrayForEach(itemIntArr, item) + { + assert(RyanJsonCompareDouble(RyanJsonGetDoubleValue(item), val)); + } + }); + } + } + } + if (RyanJsonTrue == RyanJsonIsString(pJson) && RyanJsonFuzzerShouldFail(index)) + { + if (RyanJsonTrue == RyanJsonAddStringToObject(pJson, key, RyanJsonGetStringValue(pJson))) + { + fuzzTestWithMemFail(assert( + 0 == strcmp(RyanJsonGetStringValue(RyanJsonGetObjectByKey(pJson, key)), RyanJsonGetStringValue(pJson)))); + } + + // 构造测试用 String 数组 + const char *val = RyanJsonGetStringValue(pJson); + const char *testStringArray[] = {val, val, val, val, val}; + RyanJsonBool_e jsonAddResult = RyanJsonAddItemToObject( + pJson, (0 != size % 2) ? key : "arrayString", + RyanJsonCreateStringArray(testStringArray, sizeof(testStringArray) / sizeof(testStringArray[0]))); + if (RyanJsonTrue == jsonAddResult) + { + fuzzTestWithMemFail({ + RyanJson_t itemIntArr = RyanJsonGetObjectByKey(pJson, key); + assert(RyanJsonIsArray(itemIntArr)); + assert(RyanJsonGetArraySize(itemIntArr) == sizeof(testStringArray) / sizeof(testStringArray[0])); + RyanJson_t item; + RyanJsonArrayForEach(itemIntArr, item) + { + assert(0 == strcmp(RyanJsonGetStringValue(item), val)); + } + }); + } + } + + // 复合类型递归与插入测试 + if (RyanJsonTrue == RyanJsonIsArray(pJson) || RyanJsonTrue == RyanJsonIsObject(pJson)) + { + RyanJson_t item; + // 递归处理子节点 + RyanJsonObjectForEach(pJson, item) + { + RyanJsonFuzzerTestCreate(item, size); + } + + // 添加随机生成的节点 (AddItem 方案A: 仅允许 Array/Object,成功后会消费原 item) + uint32_t oldSize = RyanJsonGetSize(pJson); + RyanJson_t newItem = RyanJsonFuzzerCreateRandomNode(pJson); + RyanJson_t newItemDup = NULL; + if (newItem && (RyanJsonIsArray(newItem) || RyanJsonIsObject(newItem))) { newItemDup = RyanJsonDuplicate(newItem); } + RyanJsonBool_e jsonAddResult = RyanJsonAddItemToObject(pJson, key, newItem); + if (RyanJsonTrue == jsonAddResult) + { + fuzzTestWithMemFail({ + assert(RyanJsonGetSize(pJson) == oldSize + 1); +#if true == RyanJsonDefaultAddAtHead + RyanJson_t itemJson = RyanJsonGetObjectByIndex(pJson, 0); +#else + RyanJson_t itemJson = RyanJsonGetObjectByIndex(pJson, oldSize); +#endif + assert(NULL != itemJson); + assert(RyanJsonIsArray(itemJson) || RyanJsonIsObject(itemJson)); + if (RyanJsonTrue == RyanJsonIsObject(pJson)) + { + assert(RyanJsonIsKey(itemJson)); + assert(0 == strcmp(RyanJsonGetKey(itemJson), key)); + } + if (newItemDup) { assert(RyanJsonCompare(itemJson, newItemDup)); } + }); + } + if (newItemDup) { RyanJsonDelete(newItemDup); } + + if (RyanJsonTrue == RyanJsonIsArray(pJson)) + { + if (RyanJsonFuzzerShouldFail(index / 8) && RyanJsonTrue == RyanJsonAddNullToArray(pJson)) + { + fuzzTestWithMemFail({ + assert(RyanJsonIsArray(pJson)); +#if true == RyanJsonDefaultAddAtHead + assert(RyanJsonIsNull(RyanJsonGetObjectByIndex(pJson, 0))); +#else + assert(RyanJsonIsNull(RyanJsonGetObjectByIndex(pJson, RyanJsonGetSize(pJson) - 1))); +#endif + }); + } + if (RyanJsonFuzzerShouldFail(index / 8) && + RyanJsonTrue == RyanJsonAddBoolToArray(pJson, 0 != size % 2 ? RyanJsonTrue : RyanJsonFalse)) + { + fuzzTestWithMemFail({ + assert(RyanJsonIsArray(pJson)); +#if true == RyanJsonDefaultAddAtHead + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, 0); +#else + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, RyanJsonGetSize(pJson) - 1); +#endif + assert(RyanJsonIsBool(item)); + assert(RyanJsonGetBoolValue(item) == (0 != size % 2 ? RyanJsonTrue : RyanJsonFalse)); + }); + } + if (RyanJsonFuzzerShouldFail(index / 8) && RyanJsonTrue == RyanJsonAddIntToArray(pJson, size)) + { + fuzzTestWithMemFail({ + assert(RyanJsonIsArray(pJson)); +#if true == RyanJsonDefaultAddAtHead + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, 0); +#else + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, RyanJsonGetSize(pJson) - 1); +#endif + assert(RyanJsonIsInt(item)); + assert(RyanJsonGetIntValue(item) == size); + }); + } + if (RyanJsonFuzzerShouldFail(index / 8) && RyanJsonTrue == RyanJsonAddDoubleToArray(pJson, size * 0.123456)) + { + fuzzTestWithMemFail({ + assert(RyanJsonIsArray(pJson)); +#if true == RyanJsonDefaultAddAtHead + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, 0); +#else + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, RyanJsonGetSize(pJson) - 1); +#endif + assert(RyanJsonIsDouble(item)); + assert(RyanJsonCompareDouble(RyanJsonGetDoubleValue(item), size * 0.123456)); + }); + } + if (RyanJsonFuzzerShouldFail(index / 8) && RyanJsonTrue == RyanJsonAddStringToArray(pJson, "NULL")) + { + fuzzTestWithMemFail({ + assert(RyanJsonIsArray(pJson)); +#if true == RyanJsonDefaultAddAtHead + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, 0); +#else + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, RyanJsonGetSize(pJson) - 1); +#endif + assert(RyanJsonIsString(item)); + assert(0 == strcmp(RyanJsonGetStringValue(item), "NULL")); + }); + } + if (RyanJsonFuzzerShouldFail(index / 8) && RyanJsonTrue == RyanJsonAddItemToArray(pJson, RyanJsonCreateArray())) + { + fuzzTestWithMemFail({ + assert(RyanJsonIsArray(pJson)); +#if true == RyanJsonDefaultAddAtHead + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, 0); +#else + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, RyanJsonGetSize(pJson) - 1); +#endif + assert(RyanJsonIsArray(item)); + }); + } + if (RyanJsonFuzzerShouldFail(index / 8) && RyanJsonTrue == RyanJsonAddItemToArray(pJson, RyanJsonCreateObject())) + { + fuzzTestWithMemFail({ + assert(RyanJsonIsArray(pJson)); +#if true == RyanJsonDefaultAddAtHead + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, 0); +#else + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, RyanJsonGetSize(pJson) - 1); +#endif + assert(RyanJsonIsObject(item)); + }); + } + + if (RyanJsonFuzzerShouldFail(index / 8)) + { + RyanJson_t randomNode = RyanJsonFuzzerCreateRandomNode(RyanJsonGetArrayValue(pJson)); + RyanJson_t randomNodeDup = NULL; + if (randomNode && (RyanJsonIsArray(randomNode) || RyanJsonIsObject(randomNode))) + { + randomNodeDup = RyanJsonDuplicate(randomNode); + } + uint32_t oldLen = RyanJsonGetSize(pJson); + if (RyanJsonTrue == RyanJsonAddItemToArray(pJson, randomNode)) + { + fuzzTestWithMemFail({ + assert(RyanJsonIsArray(pJson)); + assert(RyanJsonGetSize(pJson) == oldLen + 1); +#if true == RyanJsonDefaultAddAtHead + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, 0); +#else + RyanJson_t item = RyanJsonGetObjectByIndex(pJson, oldLen); +#endif + assert(NULL != item); + if (randomNodeDup) { assert(RyanJsonCompare(item, randomNodeDup)); } + }); + } + if (randomNodeDup) { RyanJsonDelete(randomNodeDup); } + } + + // 获取当前大小,避免重复调用 RyanJsonGetSize(其复杂度为 O(N)) + // 在嵌入式场景下应尽量规避 O(N^2) 路径 + if (RyanJsonFuzzerShouldFail(index / 8)) + { + uint32_t len = RyanJsonGetSize(pJson); + uint32_t idx = len / 2; + + // 测试中间位置插入 + // 注意:每次插入后,数组长度增加,idx 相对位置其实在变动, + // 这里为简化逻辑保持 index 不变,覆盖随机位置插入行为 + RyanJsonInsert(pJson, idx, RyanJsonCreateBool(key, 0 != size % 2 ? RyanJsonTrue : RyanJsonFalse)); + RyanJsonInsert(pJson, idx, RyanJsonCreateString(key, "NULL")); + RyanJsonInsert(pJson, idx, RyanJsonCreateInt(key, 0)); + RyanJsonInsert(pJson, idx, RyanJsonCreateDouble(key, 0)); + RyanJsonInsert(pJson, idx, RyanJsonCreateArray()); + RyanJsonInsert(pJson, idx, RyanJsonCreateObject()); + + // 测试头部插入 + RyanJsonInsert(pJson, 0, RyanJsonCreateBool(key, 0 != size % 2 ? RyanJsonTrue : RyanJsonFalse)); + RyanJsonInsert(pJson, 0, RyanJsonCreateString(key, "NULL")); + RyanJsonInsert(pJson, 0, RyanJsonCreateInt(key, 0)); + RyanJsonInsert(pJson, 0, RyanJsonCreateDouble(key, 0)); + RyanJsonInsert(pJson, 0, RyanJsonCreateArray()); + RyanJsonInsert(pJson, 0, RyanJsonCreateObject()); + } + } + } + + return RyanJsonTrue; +} diff --git a/test/fuzzer/cases/fuzzerDelete.c b/test/fuzzer/cases/fuzzerDelete.c new file mode 100644 index 0000000..917108d --- /dev/null +++ b/test/fuzzer/cases/fuzzerDelete.c @@ -0,0 +1,102 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 节点删除测试 + * + * 测试 RyanJson 的节点删除功能(DeleteByKey、DeleteByIndex)。 + * 覆盖场景: + * 异常删除:测试无效 key、越界索引、空指针等错误。 + * 特殊位置删除:测试删除头部与尾部节点。 + * 递归删除:遍历 Json 树,随机删除子节点,验证树结构的完整性。 + * + * @param state Fuzzer 状态上下文 + * @param pJson 当前正在操作的 Json 节点 + * @param size 输入数据大小 + */ +RyanJsonBool_e RyanJsonFuzzerTestDelete(RyanJson_t pJson, uint32_t size) +{ + // 仅处理容器类型(Object/Array) + if (RyanJsonFalse == RyanJsonIsArray(pJson) && RyanJsonFalse == RyanJsonIsObject(pJson)) { return RyanJsonTrue; } + + // 故障注入与异常参数测试 + if (RyanJsonFuzzerShouldFail(100)) + { + // key 删除错误用例 + RyanJsonDeleteByKey(pJson, "non_exist_key"); + RyanJsonDeleteByKey(NULL, "some_key"); + RyanJsonDeleteByKey(pJson, NULL); + RyanJsonDeleteByKey(NULL, NULL); + + // index 删除错误用例 + uint32_t currentSize = RyanJsonGetSize(pJson); + RyanJsonDeleteByIndex(pJson, + currentSize); // 越界:index 使用 0-based,size 位置必越界 + RyanJsonDeleteByIndex(NULL, size % (currentSize + 1)); + RyanJsonDeleteByIndex(pJson, (uint32_t)(-(int32_t)size)); // 负数强转 + RyanJsonDeleteByIndex(NULL, (uint32_t)(-(int32_t)size)); + } + + // 特殊位置删除测试(头部/尾部) + // 在一定概率下尝试删除头部或尾部节点,测试链表操作的鲁棒性 + uint32_t jsonSize = RyanJsonGetSize(pJson); + if (RyanJsonFuzzerShouldFail(10) && jsonSize > 2) + { + // 尝试按 key 删除尾部节点(仅 Object 有效) + if (RyanJsonTrue == RyanJsonIsObject(pJson)) + { + RyanJson_t tailNode = RyanJsonGetObjectByIndex(pJson, jsonSize - 1); + if (NULL != tailNode) { RyanJsonDeleteByKey(pJson, RyanJsonGetKey(tailNode)); } + + // 重新获取 Size,因为刚删了一个 + if (RyanJsonGetSize(pJson) > 0) + { + RyanJson_t headNode = RyanJsonGetObjectByIndex(pJson, 0); + if (NULL != headNode) { RyanJsonDeleteByKey(pJson, RyanJsonGetKey(headNode)); } + } + } + } + + // 递归遍历 + // 先递归处理子节点,再删除当前层级的节点,避免由上而下的删除导致整个分支消失,减少测试覆盖率 + RyanJson_t item = NULL; + RyanJson_t lastItem = NULL; + + RyanJsonObjectForEach(pJson, item) + { + // 递归调用 + RyanJsonFuzzerTestDelete(item, size); + lastItem = item; + } + + // 删除节点 + + // 按 key 删除(仅 Object) + if (RyanJsonTrue == RyanJsonIsObject(pJson)) + { + // 删除最后一个遍历到的子节点 + // 注意:RyanJsonObjectForEach 遍历结束后,childNode 为 NULL, + // 所以我们需要在循环中维护 lastChild。 + // 并且要确认 lastChild 仍然有效(递归处理的是它的子节点,不应影响它本身) + // 不过,如果上面的 Head/Tail 删除逻辑把 lastChild 删了,这里就会出问题。 + // 考虑到 RyanJsonGetNodeBy... 的额外开销,以及 Fuzzer 的随机性, + // 我们这里再次检查 lastChild 是否还存在于 pJson 中比较耗时。 + // 简化策略:若 lastChild 有 key,则直接尝试删除;找不到也允许返回。 + + if (NULL != lastItem && RyanJsonTrue == RyanJsonIsKey(lastItem)) { RyanJsonDeleteByKey(pJson, RyanJsonGetKey(lastItem)); } + } + + // 按 index 删除(Array/Object) + { + uint32_t idx = 0; + uint32_t currentSize = RyanJsonGetSize(pJson); + + if (0 != currentSize) + { + idx = size % currentSize; + RyanJsonDeleteByIndex(pJson, idx); + } + } + + return RyanJsonTrue; +} diff --git a/test/fuzzer/cases/fuzzerDetach.c b/test/fuzzer/cases/fuzzerDetach.c new file mode 100644 index 0000000..b869f35 --- /dev/null +++ b/test/fuzzer/cases/fuzzerDetach.c @@ -0,0 +1,83 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 节点分离测试 + * + * 测试 RyanJson 的节点分离功能(DetachByKey、DetachByIndex)。 + * 与 Delete 不同,Detach 不释放内存,而是将节点从树中摘除并返回。 + * 覆盖场景: + * 异常分离:测试无效参数、越界索引等错误。 + * 递归分离:随机分离子节点,并立即释放分离出的节点以防止内存泄漏。 + * + * @param state Fuzzer 状态上下文 + * @param pJson 当前正在操作的 Json 节点 + * @param size 输入数据大小 + */ +RyanJsonBool_e RyanJsonFuzzerTestDetach(RyanJson_t pJson, uint32_t size) +{ + // 故障注入与异常参数测试 + if (RyanJsonFuzzerShouldFail(100)) + { + // 错误的 DetachByKey 调用 + assert(RyanJsonFalse == RyanJsonDetachByKey(NULL, NULL)); + assert(RyanJsonFalse == RyanJsonDetachByKey(pJson, NULL)); + assert(RyanJsonFalse == RyanJsonDetachByKey(NULL, "NULL")); + + if (RyanJsonFalse == RyanJsonIsObject(pJson)) // 类型错误 + { + assert(NULL == RyanJsonDetachByKey(pJson, "NULL")); + } + + // 错误的 DetachByIndex 调用 + assert(NULL == RyanJsonDetachByIndex(NULL, 10)); + if (RyanJsonFalse == RyanJsonIsArray(pJson) && RyanJsonFalse == RyanJsonIsObject(pJson)) // 类型错误 + { + assert(NULL == RyanJsonDetachByIndex(pJson, 0)); + } + } + + // 仅处理容器类型(Object/Array) + if (RyanJsonFalse == RyanJsonIsArray(pJson) && RyanJsonFalse == RyanJsonIsObject(pJson)) { return RyanJsonTrue; } + + // 递归遍历 + // 先递归处理子节点 + RyanJson_t item = NULL; + RyanJson_t lastItem = NULL; + RyanJsonObjectForEach(pJson, item) + { + RyanJsonFuzzerTestDetach(item, size); + lastItem = item; + } + + // 节点分离 + + // 按 key 分离(仅 Object) + if (RyanJsonTrue == RyanJsonIsObject(pJson)) + { + // 尝试分离最后一个子节点 + if (NULL != lastItem && RyanJsonTrue == RyanJsonIsKey(lastItem)) + { + RyanJson_t detachedItem = RyanJsonDetachByKey(pJson, RyanJsonGetKey(lastItem)); + + // 重要:Detach 仅分离节点,不释放内存,必须手动 Delete 防止泄漏 + if (NULL != detachedItem) { RyanJsonDelete(detachedItem); } + } + } + + // 按 index 分离(Array/Object) + { + uint32_t idx = 0; + uint32_t currentSize = RyanJsonGetSize(pJson); + + if (0 != currentSize) + { + idx = size % currentSize; + + RyanJson_t detachedItem = RyanJsonDetachByIndex(pJson, idx); + if (NULL != detachedItem) { RyanJsonDelete(detachedItem); } + } + } + + return RyanJsonTrue; +} diff --git a/test/fuzzer/cases/fuzzerDuplicate.c b/test/fuzzer/cases/fuzzerDuplicate.c new file mode 100644 index 0000000..0e7d01d --- /dev/null +++ b/test/fuzzer/cases/fuzzerDuplicate.c @@ -0,0 +1,142 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 复制与比较测试 + * + * 测试深度复制与结构比较能力。 + * 覆盖场景: + * 深度复制正确性:验证复制后的对象与原对象在结构和值上完全一致。 + * 独立性验证:验证修改复制后的对象不会影响原对象。 + * 内存管理:验证复制过程中的内存分配与释放。 + * + * @param state Fuzzer 状态上下文 + * @param pJson 需要复制的源 Json 对象 + */ +RyanJsonBool_e RyanJsonFuzzerTestDuplicate(RyanJson_t pJson) +{ + RyanJsonBool_e result = RyanJsonTrue; + char *jsonStr = NULL; + char *jsonStrDup = NULL; + RyanJson_t pJsonDup = NULL; + + // 序列化原始对象 + uint32_t len = 0; + // 使用非格式化输出,减少空白差异干扰 + jsonStr = RyanJsonPrint(pJson, 100, RyanJsonFalse, &len); + RyanJsonCheckGotoExit(NULL != jsonStr); + + // 执行深度复制 + pJsonDup = RyanJsonDuplicate(pJson); + RyanJsonCheckGotoExit(NULL != pJsonDup); + + // 参数与边界场景测试 + // 测试对 NULL 的复制 + assert(NULL == RyanJsonDuplicate(NULL)); + assert(0 == RyanJsonGetSize(NULL)); + + // 结构完整性验证 + // 验证节点数量一致 + assert(RyanJsonGetSize(pJson) == RyanJsonGetSize(pJsonDup)); + + // 执行结构比较 + // RyanJsonCompare:比较 key 与 value + // RyanJsonCompareOnlyKey:仅比较 key(结构) + RyanJsonCompare(pJson, pJsonDup); + RyanJsonCompareOnlyKey(pJson, pJsonDup); + + // 比较接口边界测试 + assert(RyanJsonTrue == RyanJsonCompare(pJson, pJson)); // 自比较 + assert(RyanJsonFalse == RyanJsonCompare(NULL, pJsonDup)); + assert(RyanJsonFalse == RyanJsonCompare(pJson, NULL)); + assert(RyanJsonFalse == RyanJsonCompare(NULL, NULL)); // 约定:两个 NULL 不相等 + + assert(RyanJsonTrue == RyanJsonCompareOnlyKey(pJson, pJson)); + assert(RyanJsonFalse == RyanJsonCompareOnlyKey(NULL, pJsonDup)); + assert(RyanJsonFalse == RyanJsonCompareOnlyKey(pJson, NULL)); + assert(RyanJsonFalse == RyanJsonCompareOnlyKey(NULL, NULL)); + + // 序列化一致性验证 + uint32_t lenDup = 0; + jsonStrDup = RyanJsonPrint(pJsonDup, 100, RyanJsonFalse, &lenDup); + RyanJsonCheckGotoExit(NULL != jsonStrDup && lenDup > 0); + + // 验证序列化后的字符串内容完全一致 + RyanJsonCheckCode(len == lenDup && 0 == memcmp(jsonStr, jsonStrDup, (size_t)len), { + // printf("len:%" PRIu32 ", dupLen:%" PRIu32 "\r\n", len, lenDup); + // printf("jsonStr:%s, jsonStrDup:%s\r\n", jsonStr, jsonStrDup); + RyanJsonCheckGotoExit(0); + }); + + // 独立性验证 + // 修改原对象(如果可能)或修改副本,验证互不影响 + // 这里选择修改副本(pJsonDup),因为后续会销毁副本 + if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) + { + // 先执行删除操作 + RyanJsonDelete(RyanJsonDetachByIndex(pJsonDup, 0)); + if (RyanJsonGetSize(pJsonDup) > 1) { RyanJsonDelete(RyanJsonDetachByIndex(pJsonDup, 1)); } + + // 如果修改后仍是容器 + if (RyanJsonIsArray(pJsonDup) || RyanJsonIsObject(pJsonDup)) + { + RyanJson_t item; + + // 修改 key + RyanJsonObjectForEach(pJsonDup, item) + { + if (RyanJsonIsKey(item)) + { + RyanJsonChangeKey(item, "modify_key_test"); + break; // 只改第一个找到的 + } + } + + // 修改 Bool 值 + RyanJsonObjectForEach(pJsonDup, item) + { + if (RyanJsonIsBool(item)) + { + RyanJsonChangeBoolValue(item, !RyanJsonGetBoolValue(item)); + break; + } + } + + // 再次修改 Object 节点中的 key + RyanJsonObjectForEach(pJsonDup, item) + { + if (RyanJsonIsKey(item) && RyanJsonIsObject(item)) + { + RyanJsonChangeKey(item, "modify_obj_key_test"); + break; + } + } + } + + // 验证修改后的副本与原对象不再相等 + // 注意:如果原对象本来就是空的,或者修改没有实际生效(例如没找到对应类型的节点),这里可能会相等 + // 所以这里只调用比较函数增加覆盖率,不强制断言 False,因为逻辑极其复杂 + RyanJsonCompare(pJson, pJsonDup); + RyanJsonCompareOnlyKey(pJson, pJsonDup); + } + +exit__: + + if (jsonStr) + { + RyanJsonFree(jsonStr); + jsonStr = NULL; + } + if (pJsonDup) + { + RyanJsonDelete(pJsonDup); + pJsonDup = NULL; + } + if (jsonStrDup) + { + RyanJsonFree(jsonStrDup); + jsonStrDup = NULL; + } + + return result; +} diff --git a/test/fuzzer/cases/fuzzerMinify.c b/test/fuzzer/cases/fuzzerMinify.c new file mode 100644 index 0000000..076bf10 --- /dev/null +++ b/test/fuzzer/cases/fuzzerMinify.c @@ -0,0 +1,88 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 压缩测试 + * + * 测试 RyanJson 的 Minify 功能(去除空白字符)。 + * 覆盖场景: + * Minify 功能:将原始 Json 字符串压缩,验证是否能成功压缩。 + * 再次解析:解析压缩后的字符串,验证压缩未破坏 Json 结构。 + * + * @param state Fuzzer 状态上下文 + * @param data 原始输入数据字符串 + * @param size 输入数据长度 + */ +RyanJsonBool_e RyanJsonFuzzerTestMinify(const char *data, uint32_t size) +{ + // 一次性覆盖 textLen 无额外 '\0' 空间时的边界路径 + static RyanJsonBool_e minifyBoundaryCovered = RyanJsonFalse; + if (RyanJsonFalse == minifyBoundaryCovered) + { + uint8_t rawBuf[8] = {'{', '\"', 'a', '\"', ':', '1', '}', '#'}; + assert(7 == RyanJsonMinify((char *)rawBuf, 7)); + assert('#' == rawBuf[7]); + + // 返回值小于 textLen 时,应写入 '\0' + uint8_t rawBuf2[5] = {'a', ' ', 'b', 'c', '#'}; + assert(3 == RyanJsonMinify((char *)rawBuf2, 4)); + assert('a' == rawBuf2[0]); + assert('b' == rawBuf2[1]); + assert('c' == rawBuf2[2]); + assert('\0' == rawBuf2[3]); + assert('#' == rawBuf2[4]); + + assert(0 == RyanJsonMinify(NULL, 0)); + assert(0 == RyanJsonMinify(NULL, 10)); + assert(0 == RyanJsonMinify(NULL, -10)); + + minifyBoundaryCovered = RyanJsonTrue; + } + + // 准备缓冲区并拷贝数据 + // 分配比原始数据稍大的缓冲区,防止边界溢出 + char *buf = (char *)malloc(size + 100); + if (!buf) { return RyanJsonFalse; } + + memcpy(buf, data, size); + // 确保有足够的终止符与安全填充 + memset(buf + size, 0, 100); + + // 边界与异常参数测试 + // 测试负数长度输入 + assert(0 == RyanJsonMinify(buf, -10)); + + // 执行 Minify + // RyanJsonMinify 会原地修改缓冲区并移除空白字符 + uint32_t len = RyanJsonMinify(buf, (int32_t)size); + assert(len > 0); + assert(len <= size); + + // 验证 Minify 后数据有效性 + // 尝试解析压缩后的字符串,确认结构未被破坏 + // 注意:Minify 只是去空格,如果原串合法,Minify 后也应合法。 + // 这里使用可选尾部模式(不强制 Null Terminator,虽然已补终止符) + RyanJson_t pJson = RyanJsonParseOptions(buf, len, size % 2 ? RyanJsonTrue : RyanJsonFalse, NULL); + free(buf); + if (NULL != pJson) + { + // 如果解析成功,尝试打印回来,确保对象结构完整 + uint32_t lenPrint = 0; + char *jsonStr = RyanJsonPrint(pJson, 100, RyanJsonFalse, &lenPrint); + + RyanJsonCheckCode(NULL != jsonStr && lenPrint > 0, { + RyanJsonDelete(pJson); + return RyanJsonFalse; + }); + + RyanJsonFree(jsonStr); + RyanJsonDelete(pJson); + } + else + { + // 如果输入本身非法,解析失败是预期行为 + return RyanJsonFalse; + } + + return RyanJsonTrue; +} diff --git a/test/fuzzer/cases/fuzzerModify.c b/test/fuzzer/cases/fuzzerModify.c new file mode 100644 index 0000000..71d83d1 --- /dev/null +++ b/test/fuzzer/cases/fuzzerModify.c @@ -0,0 +1,277 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 修改与访问测试 + * + * 测试 RyanJson 的节点修改能力(Change 值/key)以及数据访问安全性。 + * 覆盖场景: + * 异常参数修改:测试空指针、类型不匹配时的修改行为。 + * 递归修改:遍历 Json 树,随机修改子节点的值或 key。 + * 类型安全访问:测试类型不匹配时调用 Get 接口的安全性(例如对 Int 调用 GetString)。 + * 访问正确性验证:验证 Get 接口返回值与 Set 结果一致。 + * + * @param state Fuzzer 状态上下文 + * @param pJson 当前正在操作的 Json 节点 + * @param size 输入数据大小 + */ +RyanJsonBool_e RyanJsonFuzzerTestModify(RyanJson_t pJson, uint32_t size) +{ + // 一次性覆盖 Change 参数/类型防御分支,并验证失败不改值 + static RyanJsonBool_e changeGuardCovered = RyanJsonFalse; + if (RyanJsonFalse == changeGuardCovered) + { + RyanJsonBool_e lastIsEnableMemFail = g_fuzzerState.isEnableMemFail; + g_fuzzerState.isEnableMemFail = false; + + RyanJson_t intNode = RyanJsonCreateInt(NULL, 123); + RyanJson_t doubleNode = RyanJsonCreateDouble(NULL, 1.25); + RyanJson_t boolNode = RyanJsonCreateBool("flag", RyanJsonTrue); + RyanJson_t strNode = RyanJsonCreateString("k", "v"); + + if (intNode) + { + assert(RyanJsonFalse == RyanJsonChangeDoubleValue(intNode, 9.9)); + assert(123 == RyanJsonGetIntValue(intNode)); + } + + if (doubleNode) + { + assert(RyanJsonFalse == RyanJsonChangeIntValue(doubleNode, 7)); + assert(RyanJsonTrue == RyanJsonCompareDouble(1.25, RyanJsonGetDoubleValue(doubleNode))); + } + + if (boolNode) + { + assert(RyanJsonFalse == RyanJsonChangeIntValue(boolNode, 1)); + assert(RyanJsonTrue == RyanJsonGetBoolValue(boolNode)); + assert(RyanJsonTrue == RyanJsonChangeKey(boolNode, "flag2")); + assert(0 == strcmp(RyanJsonGetKey(boolNode), "flag2")); + } + + if (strNode) + { + // 命中 RyanJsonChangeStringValue 中 RyanJsonIsKey(pJson)==true 的分支 + assert(RyanJsonTrue == RyanJsonChangeStringValue(strNode, "v2")); + assert(0 == strcmp(RyanJsonGetStringValue(strNode), "v2")); + assert(0 == strcmp(RyanJsonGetKey(strNode), "k")); + + assert(RyanJsonFalse == RyanJsonChangeBoolValue(strNode, RyanJsonFalse)); + assert(0 == strcmp(RyanJsonGetStringValue(strNode), "v2")); + assert(RyanJsonFalse == RyanJsonChangeKey(strNode, NULL)); + assert(0 == strcmp(RyanJsonGetKey(strNode), "k")); + } + + RyanJsonDelete(intNode); + RyanJsonDelete(doubleNode); + RyanJsonDelete(boolNode); + RyanJsonDelete(strNode); + + g_fuzzerState.isEnableMemFail = lastIsEnableMemFail; + changeGuardCovered = RyanJsonTrue; + } + + // 修改 key 测试 + if (RyanJsonIsKey(pJson)) + { + const char *oldKey = RyanJsonGetKey(pJson); + if (oldKey) + { + // 备份原始 key + size_t keyLen = strlen(oldKey); + char *backupKey = (char *)malloc(keyLen + 1); + if (backupKey) + { + memcpy(backupKey, oldKey, keyLen); + backupKey[keyLen] = 0; + + // 修改为临时 key + if (RyanJsonTrue == RyanJsonChangeKey(pJson, "temp_modified_key")) + { + fuzzTestWithMemFail({ assert(0 == strcmp(RyanJsonGetKey(pJson), "temp_modified_key")); }); + } + + // 恢复原始 key(确保结构一致,避免影响后续测试) + if (RyanJsonTrue == RyanJsonChangeKey(pJson, backupKey)) + { + fuzzTestWithMemFail({ assert(0 == strcmp(RyanJsonGetKey(pJson), backupKey)); }); + } + free(backupKey); + } + } + } + + // 布尔节点 + if (RyanJsonIsBool(pJson)) + { + RyanJsonBool_e oldBool = RyanJsonGetBoolValue(pJson); + if (RyanJsonTrue == RyanJsonChangeBoolValue(pJson, !oldBool)) + { + fuzzTestWithMemFail({ assert(RyanJsonGetBoolValue(pJson) == !oldBool); }); + } + if (RyanJsonTrue == RyanJsonChangeBoolValue(pJson, oldBool)) // 恢复 + { + fuzzTestWithMemFail({ assert(RyanJsonGetBoolValue(pJson) == oldBool); }); + } + } + + // 数值节点(Int/Double) + if (RyanJsonIsNumber(pJson)) + { + if (RyanJsonIsInt(pJson)) + { + int32_t oldInt = RyanJsonGetIntValue(pJson); + if (RyanJsonTrue == RyanJsonChangeIntValue(pJson, (int32_t)size)) + { + fuzzTestWithMemFail({ assert(RyanJsonGetIntValue(pJson) == (int32_t)size); }); + } + if (RyanJsonTrue == RyanJsonChangeIntValue(pJson, oldInt)) // 恢复 + { + fuzzTestWithMemFail({ assert(RyanJsonGetIntValue(pJson) == oldInt); }); + } + } + if (RyanJsonIsDouble(pJson)) + { + double oldDouble = RyanJsonGetDoubleValue(pJson); + if (RyanJsonTrue == RyanJsonChangeDoubleValue(pJson, size * 1.123456789)) + { + fuzzTestWithMemFail({ assert(RyanJsonCompareDouble(RyanJsonGetDoubleValue(pJson), size * 1.123456789)); }); + } + if (RyanJsonTrue == RyanJsonChangeDoubleValue(pJson, oldDouble)) // 恢复 + { + fuzzTestWithMemFail({ assert(RyanJsonCompareDouble(RyanJsonGetDoubleValue(pJson), oldDouble)); }); + } + } + } + + // 字符串节点 + if (RyanJsonIsString(pJson)) + { + const char *oldStr = RyanJsonGetStringValue(pJson); + if (oldStr) + { + size_t strLen = strlen(oldStr); + char *backupStr = (char *)malloc(strLen + 1); + if (backupStr) + { + memcpy(backupStr, oldStr, strLen); + backupStr[strLen] = 0; + + if (RyanJsonTrue == RyanJsonChangeStringValue(pJson, "modified_string_value")) + { + fuzzTestWithMemFail( + { assert(0 == strcmp(RyanJsonGetStringValue(pJson), "modified_string_value")); }); + } + if (RyanJsonTrue == RyanJsonChangeStringValue(pJson, "short")) + { + fuzzTestWithMemFail({ assert(0 == strcmp(RyanJsonGetStringValue(pJson), "short")); }); + } + + if (RyanJsonTrue == RyanJsonChangeStringValue(pJson, backupStr)) // 恢复 + { + fuzzTestWithMemFail({ assert(0 == strcmp(RyanJsonGetStringValue(pJson), backupStr)); }); + } + free(backupStr); + } + } + } + + // 递归遍历 + if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) + { + RyanJson_t item; + RyanJsonArrayForEach(pJson, item) + { + RyanJsonCheckReturnFalse(RyanJsonTrue == RyanJsonFuzzerTestModify(item, size)); + } + } + + return RyanJsonTrue; +} + +RyanJsonBool_e RyanJsonFuzzerVerifyGet(RyanJson_t parent, RyanJson_t current, uint32_t index, uint32_t size) +{ + (void)size; // 未使用 + + RyanJsonIsNull(current); + + // Get 接口前置条件:调用方需保证 pJson 非 NULL 且类型匹配。 + // 这里只保留可明确约定返回值的 GetObjectByKey 参数防御验证。 + + // 验证 GetObjectByKey 异常参数 + assert(NULL == RyanJsonGetObjectByKey(NULL, NULL)); + assert(NULL == RyanJsonGetObjectByKey(current, NULL)); + assert(NULL == RyanJsonGetObjectByKey(NULL, "NULL")); + + assert(NULL == RyanJsonGetObjectToKey(NULL, NULL)); + assert(NULL == RyanJsonGetObjectToKey(current, NULL)); + assert(NULL == RyanJsonGetObjectToKey(NULL, "NULL")); + + // 验证 GetObjectByIndex 异常参数 + assert(NULL == RyanJsonGetObjectByIndex(NULL, 10)); + + // 验证错误类型调用 + if (!RyanJsonIsObject(current)) { assert(NULL == RyanJsonGetObjectByKey(current, "NULL")); } + + // 验证 GetObjectByIndex 错误类型调用 + if (!RyanJsonIsArray(current) && !RyanJsonIsObject(current)) { assert(NULL == RyanJsonGetObjectByIndex(current, 0)); } + + // 验证父子关系查找 + // 确认当前节点 current 可以通过 parent + key/index 找回 + if (RyanJsonIsKey(current)) + { + // 如果是 key 类型,应能通过 parent + key 找回(或找回其对应值) + // 注意:RyanJsonGetObjectByKey 通常返回值节点,而非 key 节点本身 + // 但在 RyanJson 实现中,key 节点与 value 节点关系较紧密,返回语义由实现细节决定 + // 这里只调用 API 增加覆盖率,不做强一致性断言,因为实现细节可能复杂 + RyanJsonGetObjectToKey(parent, RyanJsonGetKey(current)); + } + else + { + // 否则尝试通过 index 访问 + RyanJsonGetObjectToIndex(parent, index); + } + + // 递归验证 + if (RyanJsonIsArray(current) || RyanJsonIsObject(current)) + { + RyanJson_t item; + // 这里的 index 是相对于 current 的子索引 + + uint32_t childIndex = 0; + RyanJsonObjectForEach(current, item) + { + RyanJsonFuzzerVerifyGet(current, item, childIndex, size); + childIndex++; + } + } + + return RyanJsonTrue; +} + +RyanJsonBool_e RyanJsonFuzzerTestGet(RyanJson_t pJson, uint32_t size) +{ + // 全局异常测试 + assert(RyanJsonFalse == RyanJsonIsKey(NULL)); + assert(RyanJsonFalse == RyanJsonIsNull(NULL)); + assert(RyanJsonFalse == RyanJsonIsBool(NULL)); + assert(RyanJsonFalse == RyanJsonIsNumber(NULL)); + assert(RyanJsonFalse == RyanJsonIsString(NULL)); + assert(RyanJsonFalse == RyanJsonIsArray(NULL)); + assert(RyanJsonFalse == RyanJsonIsObject(NULL)); + assert(RyanJsonFalse == RyanJsonIsInt(NULL)); + assert(RyanJsonFalse == RyanJsonIsDouble(NULL)); + + // 遍历测试 + if (RyanJsonIsArray(pJson) || RyanJsonIsObject(pJson)) + { + RyanJson_t item; + uint32_t index = 0; + RyanJsonObjectForEach(pJson, item) + { + RyanJsonFuzzerVerifyGet(pJson, item, index, size); + index++; + } + } + return RyanJsonTrue; +} diff --git a/test/fuzzer/cases/fuzzerParse.c b/test/fuzzer/cases/fuzzerParse.c new file mode 100644 index 0000000..ad3acf9 --- /dev/null +++ b/test/fuzzer/cases/fuzzerParse.c @@ -0,0 +1,420 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 解析与打印测试 + * + * 测试 RyanJson 的核心解析与打印路径。 + * 核心逻辑: + * 错误注入测试:在内存分配失败场景下验证打印、复制与比较路径的健壮性。 + * 往返一致性验证: + * - Parse(Print(Parse(Data))) 与 Parse(Data) 语义一致。 + * - 验证打印结果再次解析后,结构和内容保持稳定。 + * 预分配缓冲区打印测试:验证 PrintPreallocated 在不同容量下的行为。 + * + * @param state Fuzzer 状态上下文 + * @param pJson 解析输入数据得到的初始 Json 对象 + * @param data 原始输入数据字符串 + * @param size 输入数据长度 + */ +RyanJsonBool_e RyanJsonFuzzerTestParse(RyanJson_t pJson, const char *data, uint32_t size) +{ + // 一次性覆盖关键防御分支,避免依赖随机路径命中 + static RyanJsonBool_e coreBoundaryCovered = RyanJsonFalse; + if (RyanJsonFalse == coreBoundaryCovered) + { + RyanJsonBool_e lastIsEnableMemFail = g_fuzzerState.isEnableMemFail; + g_fuzzerState.isEnableMemFail = false; + + // 指数累积溢出防御 + assert(NULL == RyanJsonParse("1e2147483648")); + assert(NULL == RyanJsonParse("1e-2147483648")); + // 边界值:驱动 e_scale==INT32_MAX/10 且 digit<=INT32_MAX%10 的分支 + assert(NULL == RyanJsonParse("1e2147483647")); + // 负指数边界会下溢到 0(有限数),属于可接受输入 + RyanJson_t negativeBoundary = RyanJsonParse("1e-2147483647"); + assert(NULL != negativeBoundary); + RyanJsonDelete(negativeBoundary); + assert(NULL == RyanJsonParse("{\"a\":1e2147483648}")); +#if true == RyanJsonStrictObjectKeyCheck + assert(NULL == RyanJsonParse("{\"dup\":1,\"dup\":2}")); +#else + { + RyanJson_t dupObj = RyanJsonParse("{\"dup\":1,\"dup\":2}"); + assert(NULL != dupObj); + RyanJsonDelete(dupObj); + } +#endif + + // 比较逻辑分支覆盖: + // 根节点类型不一致,覆盖类型比较失败分支。 + // boolValue 不一致,覆盖布尔全量比较失败分支。 + // Int/Double 数值不一致,覆盖数值全量比较失败分支。 + // strValue 不一致,覆盖字符串全量比较失败分支。 + // 对象 key 乱序,覆盖对象下沉/同层 key 回退查找分支。 + // 对象缺 key,覆盖 rightChild == NULL 的失败分支。 + { + RyanJson_t cmpInt = RyanJsonCreateInt(NULL, 1); + RyanJson_t cmpString = RyanJsonCreateString(NULL, "1"); + assert(NULL != cmpInt && NULL != cmpString); + assert(RyanJsonFalse == RyanJsonCompare(cmpInt, cmpString)); + assert(RyanJsonFalse == RyanJsonCompareOnlyKey(cmpInt, cmpString)); + RyanJsonDelete(cmpInt); + RyanJsonDelete(cmpString); + + RyanJson_t cmpBoolTrue = RyanJsonCreateBool(NULL, RyanJsonTrue); + RyanJson_t cmpBoolFalse = RyanJsonCreateBool(NULL, RyanJsonFalse); + assert(NULL != cmpBoolTrue && NULL != cmpBoolFalse); + assert(RyanJsonFalse == RyanJsonCompare(cmpBoolTrue, cmpBoolFalse)); + assert(RyanJsonTrue == RyanJsonCompareOnlyKey(cmpBoolTrue, cmpBoolFalse)); + RyanJsonDelete(cmpBoolTrue); + RyanJsonDelete(cmpBoolFalse); + + RyanJson_t cmpIntLeft = RyanJsonCreateInt(NULL, 123); + RyanJson_t cmpIntRight = RyanJsonCreateInt(NULL, 456); + assert(NULL != cmpIntLeft && NULL != cmpIntRight); + assert(RyanJsonFalse == RyanJsonCompare(cmpIntLeft, cmpIntRight)); + assert(RyanJsonTrue == RyanJsonCompareOnlyKey(cmpIntLeft, cmpIntRight)); + RyanJsonDelete(cmpIntLeft); + RyanJsonDelete(cmpIntRight); + + RyanJson_t cmpDoubleLeft = RyanJsonCreateDouble(NULL, 1.0); + RyanJson_t cmpDoubleRight = RyanJsonCreateDouble(NULL, 2.0); + assert(NULL != cmpDoubleLeft && NULL != cmpDoubleRight); + assert(RyanJsonFalse == RyanJsonCompare(cmpDoubleLeft, cmpDoubleRight)); + assert(RyanJsonTrue == RyanJsonCompareOnlyKey(cmpDoubleLeft, cmpDoubleRight)); + RyanJsonDelete(cmpDoubleLeft); + RyanJsonDelete(cmpDoubleRight); + + RyanJson_t cmpStrLeft = RyanJsonCreateString(NULL, "alpha"); + RyanJson_t cmpStrRight = RyanJsonCreateString(NULL, "beta"); + assert(NULL != cmpStrLeft && NULL != cmpStrRight); + assert(RyanJsonFalse == RyanJsonCompare(cmpStrLeft, cmpStrRight)); + assert(RyanJsonTrue == RyanJsonCompareOnlyKey(cmpStrLeft, cmpStrRight)); + RyanJsonDelete(cmpStrLeft); + RyanJsonDelete(cmpStrRight); + + RyanJson_t objLeft = RyanJsonCreateObject(); + RyanJson_t objRightUnordered = RyanJsonCreateObject(); + assert(NULL != objLeft && NULL != objRightUnordered); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLeft, "a", 1)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLeft, "b", 2)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLeft, "c", 3)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objRightUnordered, "b", 2)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objRightUnordered, "a", 1)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objRightUnordered, "c", 3)); + assert(RyanJsonTrue == RyanJsonCompare(objLeft, objRightUnordered)); + assert(RyanJsonTrue == RyanJsonCompareOnlyKey(objLeft, objRightUnordered)); + RyanJsonDelete(objLeft); + RyanJsonDelete(objRightUnordered); + + RyanJson_t objNeedKey = RyanJsonCreateObject(); + RyanJson_t objMissKey = RyanJsonCreateObject(); + assert(NULL != objNeedKey && NULL != objMissKey); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objNeedKey, "a", 1)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objMissKey, "b", 1)); + assert(RyanJsonFalse == RyanJsonCompare(objNeedKey, objMissKey)); + assert(RyanJsonFalse == RyanJsonCompareOnlyKey(objNeedKey, objMissKey)); + RyanJsonDelete(objNeedKey); + RyanJsonDelete(objMissKey); + + // 命中同层快路径中 rightCandidate == NULL 的分支: + // 左侧首 key=a,右侧将 a 放到最后;第一次下沉后 rightCurrent 位于尾节点。 + // 比较 leftNext 时 rightCandidate 为 NULL,必须走按 key 回退查找。 + RyanJson_t objLastHitLeft = RyanJsonCreateObject(); + RyanJson_t objLastHitRight = RyanJsonCreateObject(); + assert(NULL != objLastHitLeft && NULL != objLastHitRight); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLastHitLeft, "a", 1)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLastHitLeft, "b", 2)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLastHitLeft, "c", 3)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLastHitRight, "b", 2)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLastHitRight, "c", 3)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objLastHitRight, "a", 1)); + assert(RyanJsonTrue == RyanJsonCompare(objLastHitLeft, objLastHitRight)); + assert(RyanJsonTrue == RyanJsonCompareOnlyKey(objLastHitLeft, objLastHitRight)); + RyanJsonDelete(objLastHitLeft); + RyanJsonDelete(objLastHitRight); + + // 命中同层回退查找失败(rightNext == NULL)分支: + // 首 key 相同、后续 key 不同且 size 相同,失败点落在同层阶段而非下沉阶段。 + RyanJson_t objPrefixLeft = RyanJsonCreateObject(); + RyanJson_t objPrefixRight = RyanJsonCreateObject(); + assert(NULL != objPrefixLeft && NULL != objPrefixRight); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objPrefixLeft, "a", 1)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objPrefixLeft, "b", 2)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objPrefixRight, "a", 1)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(objPrefixRight, "c", 2)); + assert(RyanJsonFalse == RyanJsonCompare(objPrefixLeft, objPrefixRight)); + assert(RyanJsonFalse == RyanJsonCompareOnlyKey(objPrefixLeft, objPrefixRight)); + RyanJsonDelete(objPrefixLeft); + RyanJsonDelete(objPrefixRight); + + // 容器内数值子类型:全量比较要求一致,仅比较 key 时允许 int/double 混用。 + RyanJson_t numberSubtypeLeft = RyanJsonParse("{\"n\":1,\"arr\":[1,2.0],\"obj\":{\"x\":3,\"y\":4.0}}"); + RyanJson_t numberSubtypeRight = RyanJsonParse("{\"obj\":{\"y\":4,\"x\":3.0},\"arr\":[1.0,2],\"n\":1.0}"); + assert(NULL != numberSubtypeLeft && NULL != numberSubtypeRight); + assert(RyanJsonFalse == RyanJsonCompare(numberSubtypeLeft, numberSubtypeRight)); + assert(RyanJsonTrue == RyanJsonCompareOnlyKey(numberSubtypeLeft, numberSubtypeRight)); + RyanJsonDelete(numberSubtypeLeft); + RyanJsonDelete(numberSubtypeRight); + } + + // int32 打印边界:11(不含 \0 空间)失败,12(含 \0 空间)成功 + RyanJson_t intItem = RyanJsonCreateInt(NULL, INT32_MIN); + assert(NULL != intItem); + char tooSmall[11] = {0}; + assert(NULL == RyanJsonPrintPreallocated(intItem, tooSmall, sizeof(tooSmall), RyanJsonFalse, NULL)); + char exactFit[12] = {0}; + char *exactOut = RyanJsonPrintPreallocated(intItem, exactFit, sizeof(exactFit), RyanJsonFalse, NULL); + assert(NULL != exactOut); + assert(0 == strcmp(exactOut, "-2147483648")); + RyanJsonDelete(intItem); + + // 预分配“刚好够用”的成功路径(非数值节点) + { + RyanJson_t strObj = RyanJsonCreateObject(); + assert(NULL != strObj); + assert(RyanJsonTrue == RyanJsonAddStringToObject(strObj, "k", "v")); + + uint32_t expectedLen = 0; + char *expected = RyanJsonPrint(strObj, 0, RyanJsonFalse, &expectedLen); + assert(NULL != expected); + + char exactBuf[16] = {0}; + assert(expectedLen + 1U <= sizeof(exactBuf)); + char *exactStr = RyanJsonPrintPreallocated(strObj, exactBuf, expectedLen + 1U, RyanJsonFalse, NULL); + assert(NULL != exactStr); + assert(0 == strcmp(expected, exactStr)); + + RyanJsonFree(expected); + RyanJsonDelete(strObj); + } + + g_fuzzerState.isEnableMemFail = lastIsEnableMemFail; + coreBoundaryCovered = RyanJsonTrue; + } + + if (RyanJsonFuzzerShouldFail(100)) + { + assert(1 == RyanJsonInternalCalcLenBytes(UINT8_MAX - 1)); + assert(1 == RyanJsonInternalCalcLenBytes(UINT8_MAX)); + assert(2 == RyanJsonInternalCalcLenBytes((uint32_t)UINT8_MAX + 1U)); + assert(2 == RyanJsonInternalCalcLenBytes(UINT16_MAX)); + assert(3 == RyanJsonInternalCalcLenBytes(UINT32_MAX - 1)); + + assert(1 == RyanJsonInternalDecodeKeyLenField(0x01)); + assert(2 == RyanJsonInternalDecodeKeyLenField(0x02)); + assert(4 == RyanJsonInternalDecodeKeyLenField(0x03)); + + g_fuzzerState.isEnableMemFail = false; // 临时禁用内存失败模拟,便于构造测试对象 + RyanJson_t objItem = RyanJsonCreateObject(); + RyanJson_t objItem2 = RyanJsonCreateObject(); + + // 故意设置错误的类型标志,测试鲁棒性 + RyanJsonSetType(objItem, 0); + RyanJsonSetType(objItem2, 0); + + // 验证异常状态下的 API 行为 + assert(NULL == RyanJsonPrint(objItem, 100, RyanJsonFalse, NULL)); + assert(NULL == RyanJsonDuplicate(objItem)); + assert(RyanJsonFalse == RyanJsonCompare(objItem, objItem2)); + assert(RyanJsonFalse == RyanJsonCompareOnlyKey(objItem, objItem2)); + + // 测试 Insert 类型检查 + assert(RyanJsonFalse == RyanJsonInsert(objItem, 0, RyanJsonCreateString("key", "true"))); + assert(RyanJsonFalse == RyanJsonInsert(objItem2, UINT32_MAX, RyanJsonCreateString("key", "true"))); + + // 恢复正确类型 + RyanJsonSetType(objItem, RyanJsonTypeObject); + RyanJsonSetType(objItem2, RyanJsonTypeObject); + + // 测试 Insert 异常参数 + if (RyanJsonIsObject(pJson)) + { + assert(RyanJsonFalse == RyanJsonInsert(pJson, 0, RyanJsonCreateString(NULL, "true"))); // 缺少 key + assert(RyanJsonFalse == RyanJsonInsert(pJson, UINT32_MAX, RyanJsonCreateString(NULL, "true"))); + } + + // 正常插入尝试 + if (RyanJsonIsArray(pJson)) { assert(RyanJsonTrue == RyanJsonInsert(pJson, 0, RyanJsonCreateString(NULL, "true"))); } + else if (RyanJsonIsObject(pJson)) + { + // 对象 key 需唯一:为插入构造一个未使用的 key + RyanJsonBool_e foundUniqueKey = RyanJsonFalse; + char keyBuf[32]; + + for (uint32_t i = 0; i < 128; i++) + { + if (0 == i) { RyanJsonSnprintf(keyBuf, sizeof(keyBuf), "key"); } + else + { + RyanJsonSnprintf(keyBuf, sizeof(keyBuf), "__fuzz_key_%u__", (unsigned)i); + } + + if (RyanJsonFalse == RyanJsonHasObjectByKey(pJson, keyBuf)) + { + foundUniqueKey = RyanJsonTrue; + break; + } + } + + if (foundUniqueKey) + { + RyanJson_t insertItem = RyanJsonCreateString(keyBuf, "true"); + assert(NULL != insertItem); + assert(RyanJsonTrue == RyanJsonInsert(pJson, 0, insertItem)); + } + } + + RyanJsonDelete(objItem); + RyanJsonDelete(objItem2); + g_fuzzerState.isEnableMemFail = true; // 重新启用内存失败模拟 + + // 验证 Print 系列函数的空指针处理 + assert(NULL == RyanJsonPrint(NULL, 100, RyanJsonFalse, NULL)); + assert(NULL == RyanJsonPrintWithStyle(pJson, 100, NULL, NULL)); + assert(NULL == RyanJsonPrintPreallocated(NULL, NULL, 100, RyanJsonFalse, NULL)); + assert(NULL == RyanJsonPrintPreallocated(pJson, NULL, 100, RyanJsonFalse, NULL)); + assert(NULL == RyanJsonPrintPreallocated(NULL, (char *)data, 100, RyanJsonFalse, NULL)); + assert(NULL == RyanJsonPrintPreallocated(pJson, (char *)data, 0, RyanJsonFalse, NULL)); + assert(NULL == RyanJsonPrintPreallocatedWithStyle(pJson, (char *)data, 100, NULL, NULL)); + + assert(NULL == RyanJsonParse(NULL)); + + // 特殊 doubleValue 打印(NaN / Infinity 输出为 null) + { + RyanJson_t specialObj = RyanJsonCreateObject(); + if (specialObj) + { + g_fuzzerState.isEnableMemFail = false; + RyanJsonAddDoubleToObject(specialObj, "inf", INFINITY); + RyanJsonAddDoubleToObject(specialObj, "ninf", -INFINITY); + RyanJsonAddDoubleToObject(specialObj, "nan", NAN); + g_fuzzerState.isEnableMemFail = true; + + uint32_t specialLen = 0; + char *specialStr = RyanJsonPrint(specialObj, 100, RyanJsonFalse, &specialLen); + if (specialStr) + { + assert(NULL != strstr(specialStr, "\"inf\":null")); + assert(NULL != strstr(specialStr, "\"ninf\":null")); + assert(NULL != strstr(specialStr, "\"nan\":null")); + RyanJsonFree(specialStr); + } + + RyanJsonDelete(specialObj); + } + } + + // 数值溢出路径覆盖(int/double) + { + g_fuzzerState.isEnableMemFail = false; + + uint32_t hugeIntLen = 600; + uint32_t hugeFracLen = 1000; + char *hugeBuf = (char *)malloc(hugeFracLen); + if (hugeBuf) + { + memset(hugeBuf, '9', (size_t)(hugeIntLen - 1)); + hugeBuf[0] = '1'; + hugeBuf[hugeIntLen - 1] = '\0'; + + RyanJson_t hugeIntJson = RyanJsonParse(hugeBuf); + assert(NULL == hugeIntJson); + if (hugeIntJson) { RyanJsonDelete(hugeIntJson); } + + hugeBuf[0] = '0'; + hugeBuf[1] = '.'; + memset(hugeBuf + 2, '9', (size_t)(hugeFracLen - 3)); + hugeBuf[hugeFracLen - 1] = '\0'; + + RyanJson_t hugeFracJson = RyanJsonParse(hugeBuf); + assert(NULL == hugeFracJson); + if (hugeFracJson) { RyanJsonDelete(hugeFracJson); } + + free(hugeBuf); + g_fuzzerState.isEnableMemFail = true; + } + } + } + + RyanJsonBool isPrintFormat = size % 2 ? RyanJsonFalse : RyanJsonTrue; + if (size > 1024) { isPrintFormat = RyanJsonFalse; } + + if (size > 512) { return RyanJsonTrue; } + + // 序列化一致性校验 + uint32_t len = 0; + // 随机选择是否格式化,增加覆盖面 + char *jsonStr = RyanJsonPrint(pJson, isPrintFormat ? size : 0, isPrintFormat, &len); + RyanJsonCheckReturnFalse(NULL != jsonStr); + + // 验证不传长度指针的情况 + g_fuzzerState.isEnableMemFail = false; + char *jsonStrCopy = RyanJsonPrint(pJson, isPrintFormat ? size : 0, isPrintFormat, NULL); + g_fuzzerState.isEnableMemFail = true; + assert(0 == strncmp(jsonStr, jsonStrCopy, len)); + RyanJsonFree(jsonStrCopy); + RyanJsonFree(jsonStr); + + // 预分配缓冲区打印测试 + // 验证在有限缓冲区、刚好够用缓冲区等情况下的打印行为 + uint32_t bufLen = len * 1.2; + if (bufLen < size * 1.5) { bufLen = size * 1.5; } + if (bufLen < 1024) { bufLen = 1024; } + + char *buf = (char *)malloc((size_t)bufLen); + if (!buf) { return RyanJsonFalse; } + + { + uint32_t len2 = 0; + // RyanJson 内部可能返回失败 + g_fuzzerState.isEnableMemFail = false; + char *jsonStr2 = RyanJsonPrintPreallocated(pJson, buf, bufLen, isPrintFormat, &len2); + g_fuzzerState.isEnableMemFail = true; + assert(NULL != jsonStr2); + assert(len == len2); + // assert(0 == strncmp(jsonStr, jsonStr2, len)); + } + + // 解析原始数据测试 + // 注意:输入数据不一定以 \0 结尾,因此需要手动补终止符 + memcpy(buf, data, (size_t)size); + buf[size] = 0; + + RyanJson_t jsonRoot = RyanJsonParse(buf); + RyanJsonCheckCode(NULL != jsonRoot, { + free(buf); + return RyanJsonFalse; + }); + + // 二次序列化校验 + // 这里验证 parsedRoot 可被稳定打印。 + // 仅当原始输入是合法 Json 且格式一致时,字符串内容才可能完全一致。 + { + uint32_t len3 = 0; + // 使用与第一次打印相同的参数 + char *jsonStr3 = RyanJsonPrint(jsonRoot, 100, size % 2 ? RyanJsonFalse : RyanJsonTrue, &len3); + + // 这里只校验非空,因为原始 data 可能包含冗余空白或格式差异, + // 导致 Parse 后再 Print 与原始 data 不完全一致是正常现象。 + // 这里主要确认解析结果可被稳定打印。 + RyanJsonCheckCode(NULL != jsonStr3, { + free(buf); + if (jsonStr3) { RyanJsonFree(jsonStr3); } + RyanJsonDelete(jsonRoot); + return RyanJsonFalse; + }); + + RyanJsonFree(jsonStr3); + } + + // 边界测试:缓冲区极小的情况 + { + RyanJsonPrintPreallocated(jsonRoot, buf, bufLen / 15, RyanJsonTrue, NULL); + } + + free(buf); + RyanJsonDelete(jsonRoot); + return RyanJsonTrue; +} diff --git a/test/fuzzer/cases/fuzzerReplace.c b/test/fuzzer/cases/fuzzerReplace.c new file mode 100644 index 0000000..c4418bf --- /dev/null +++ b/test/fuzzer/cases/fuzzerReplace.c @@ -0,0 +1,264 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 节点替换测试 + * + * 测试 RyanJson 的节点替换功能(ReplaceByKey、ReplaceByIndex)。 + * 覆盖场景: + * 异常替换:测试无效参数、类型不匹配、越界索引等错误。 + * 递归替换:遍历 Json 树,随机替换子节点为新生成的随机节点。 + * 内存管理:确保替换操作后,被替换的节点被正确释放,新节点被正确挂载。 + * + * @param state Fuzzer 状态上下文 + * @param pJson 当前正在操作的 Json 节点 + * @param size 输入数据大小 + */ +RyanJsonBool_e RyanJsonFuzzerTestReplace(RyanJson_t pJson, uint32_t size) +{ + // 一次性覆盖对象 ReplaceByIndex 重复 key 防御分支 + static RyanJsonBool_e replaceConflictCovered = RyanJsonFalse; + if (RyanJsonFalse == replaceConflictCovered) + { + RyanJsonBool_e lastIsEnableMemFail = g_fuzzerState.isEnableMemFail; + g_fuzzerState.isEnableMemFail = false; + + RyanJson_t obj = RyanJsonCreateObject(); + assert(NULL != obj); + assert(RyanJsonTrue == RyanJsonAddIntToObject(obj, "a", 1)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(obj, "b", 2)); + + RyanJson_t newItem = RyanJsonCreateInt("a", 9); + assert(NULL != newItem); +#if true == RyanJsonDefaultAddAtHead + uint32_t replaceIndex = 0; +#else + uint32_t replaceIndex = 1; +#endif + +#if true == RyanJsonStrictObjectKeyCheck + assert(RyanJsonFalse == RyanJsonReplaceByIndex(obj, replaceIndex, newItem)); + RyanJsonDelete(newItem); + assert(2 == RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "b"))); +#else + assert(RyanJsonTrue == RyanJsonReplaceByIndex(obj, replaceIndex, newItem)); + assert(NULL == RyanJsonGetObjectByKey(obj, "b")); +#if true == RyanJsonDefaultAddAtHead + assert(9 == RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); +#else + assert(1 == RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); +#endif +#endif + + // 同 key 替换应成功(覆盖冲突检测的 skipItem 分支) + newItem = RyanJsonCreateInt("b", 99); + assert(NULL != newItem); + assert(RyanJsonTrue == RyanJsonReplaceByIndex(obj, replaceIndex, newItem)); + assert(99 == RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "b"))); + + RyanJsonDelete(obj); + g_fuzzerState.isEnableMemFail = lastIsEnableMemFail; + replaceConflictCovered = RyanJsonTrue; + } + + // 一次性覆盖“通过 Replace 修改 value 类型”的推荐用法 + static RyanJsonBool_e replaceTypeSwitchCovered = RyanJsonFalse; + if (RyanJsonFalse == replaceTypeSwitchCovered) + { + RyanJsonBool_e lastIsEnableMemFail = g_fuzzerState.isEnableMemFail; + g_fuzzerState.isEnableMemFail = false; + + RyanJson_t obj = RyanJsonCreateObject(); + assert(NULL != obj); + assert(RyanJsonTrue == RyanJsonAddIntToObject(obj, "k", 1)); + + assert(RyanJsonTrue == RyanJsonReplaceByKey(obj, "k", RyanJsonCreateObject())); + RyanJson_t item = RyanJsonGetObjectByKey(obj, "k"); + assert(NULL != item && RyanJsonTrue == RyanJsonIsObject(item)); + assert(RyanJsonTrue == RyanJsonAddIntToObject(item, "x", 7)); + + assert(RyanJsonTrue == RyanJsonReplaceByKey(obj, "k", RyanJsonCreateArray())); + item = RyanJsonGetObjectByKey(obj, "k"); + assert(NULL != item && RyanJsonTrue == RyanJsonIsArray(item)); + + RyanJson_t arr = RyanJsonCreateArray(); + assert(NULL != arr); + assert(RyanJsonTrue == RyanJsonAddIntToArray(arr, 1)); + assert(RyanJsonTrue == RyanJsonReplaceByIndex(arr, 0, RyanJsonCreateObject())); + item = RyanJsonGetObjectByIndex(arr, 0); + assert(NULL != item && RyanJsonTrue == RyanJsonIsObject(item)); + + RyanJsonDelete(arr); + RyanJsonDelete(obj); + + g_fuzzerState.isEnableMemFail = lastIsEnableMemFail; + replaceTypeSwitchCovered = RyanJsonTrue; + } + + // 故障注入与异常参数测试 + if (RyanJsonFuzzerShouldFail(100)) + { + g_fuzzerState.isEnableMemFail = false; // 临时禁用内存失败模拟,确保测试对象创建成功 + + RyanJson_t strItem = RyanJsonCreateString("", "NULL"); + + // key 替换异常测试 + assert(RyanJsonFalse == RyanJsonReplaceByKey(NULL, NULL, NULL)); + assert(RyanJsonFalse == RyanJsonReplaceByKey(pJson, NULL, NULL)); + assert(RyanJsonFalse == RyanJsonReplaceByKey(NULL, "NULL", NULL)); + assert(RyanJsonFalse == RyanJsonReplaceByKey(NULL, NULL, strItem)); + assert(RyanJsonFalse == RyanJsonReplaceByKey(pJson, "NULL", NULL)); + assert(RyanJsonFalse == RyanJsonReplaceByKey(NULL, "NULL", strItem)); + + // 类型错误测试:非对象调用 ReplaceByKey + if (RyanJsonFalse == RyanJsonIsObject(RyanJsonGetObjectByIndex(pJson, 0))) + { + // 如果意外成功(说明 pJson 其实是对象且碰巧存在该 key), + // 需要把 strItem 取回,避免后续 RyanJsonDelete(strItem) 触发重复释放。 + if (RyanJsonTrue == RyanJsonReplaceByKey(pJson, "NULL", strItem)) { strItem = RyanJsonDetachByKey(pJson, "NULL"); } + } + + // index 替换异常测试 + assert(RyanJsonFalse == RyanJsonReplaceByIndex(NULL, 0, NULL)); + assert(RyanJsonFalse == RyanJsonReplaceByIndex(pJson, 0, NULL)); + assert(RyanJsonFalse == RyanJsonReplaceByIndex(NULL, 0, strItem)); + assert(RyanJsonFalse == RyanJsonReplaceByIndex(pJson, 0, NULL)); + assert(RyanJsonFalse == RyanJsonReplaceByIndex(NULL, 0, strItem)); + + // 类型错误测试:非容器调用 ReplaceByIndex + if (RyanJsonFalse == RyanJsonIsArray(pJson) && RyanJsonFalse == RyanJsonIsObject(pJson)) + { + assert(RyanJsonFalse == RyanJsonReplaceByIndex(pJson, 0, strItem)); + } + + // 构造临时对象,测试不存在 key 和越界 index + RyanJson_t objItem = RyanJsonCreateObject(); + assert(RyanJsonFalse == RyanJsonReplaceByKey(objItem, "NULL", strItem)); + assert(RyanJsonFalse == RyanJsonReplaceByIndex(objItem, 0, strItem)); + + RyanJsonAddItemToObject(objItem, "item", RyanJsonCreateObject()); + assert(RyanJsonFalse == RyanJsonReplaceByKey(objItem, "NULL222", strItem)); + assert(RyanJsonFalse == RyanJsonReplaceByIndex(objItem, INT32_MAX, strItem)); + + // Replace 失败后,item 仍应保持游离态(由调用方继续持有) + { + RyanJson_t keepObjItem = RyanJsonCreateInt("keep", 1); + assert(NULL != keepObjItem); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(keepObjItem)); + assert(RyanJsonFalse == RyanJsonReplaceByKey(objItem, "not_found", keepObjItem)); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(keepObjItem)); + RyanJsonDelete(keepObjItem); + + RyanJson_t arrayItem = RyanJsonCreateArray(); + assert(NULL != arrayItem); + assert(RyanJsonTrue == RyanJsonAddIntToArray(arrayItem, 9)); + + RyanJson_t keepArrItem = RyanJsonCreateString(NULL, "keep"); + assert(NULL != keepArrItem); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(keepArrItem)); + assert(RyanJsonFalse == RyanJsonReplaceByIndex(arrayItem, 7, keepArrItem)); + assert(RyanJsonTrue == RyanJsonIsDetachedItem(keepArrItem)); + + RyanJsonDelete(keepArrItem); + RyanJsonDelete(arrayItem); + } + + // 已挂树的 item 不应被 Replace + { + RyanJson_t objA = RyanJsonCreateObject(); + RyanJson_t objB = RyanJsonCreateObject(); + RyanJsonAddIntToObject(objA, "a", 1); + RyanJsonAddIntToObject(objB, "b", 2); + + RyanJson_t attachedObjItem = RyanJsonGetObjectByKey(objA, "a"); + assert(attachedObjItem); + assert(RyanJsonFalse == RyanJsonIsDetachedItem(attachedObjItem)); + assert(RyanJsonFalse == RyanJsonReplaceByKey(objB, "b", attachedObjItem)); + assert(2 == RyanJsonGetIntValue(RyanJsonGetObjectByKey(objB, "b"))); + + RyanJson_t arr1 = RyanJsonCreateArray(); + RyanJson_t arr2 = RyanJsonCreateArray(); + RyanJsonAddIntToArray(arr1, 10); + RyanJsonAddIntToArray(arr2, 20); + + RyanJson_t attachedArrItem = RyanJsonGetObjectByIndex(arr1, 0); + assert(attachedArrItem); + assert(RyanJsonFalse == RyanJsonIsDetachedItem(attachedArrItem)); + assert(RyanJsonFalse == RyanJsonReplaceByIndex(arr2, 0, attachedArrItem)); + assert(20 == RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr2, 0))); + + RyanJsonDelete(objA); + RyanJsonDelete(objB); + RyanJsonDelete(arr1); + RyanJsonDelete(arr2); + } + + g_fuzzerState.isEnableMemFail = true; // 恢复状态 + + RyanJsonDelete(objItem); + RyanJsonDelete(strItem); + } + + // 递归遍历与替换 + // 仅处理容器类型(Object/Array) + if (RyanJsonFalse == RyanJsonIsArray(pJson) && RyanJsonFalse == RyanJsonIsObject(pJson)) { return RyanJsonTrue; } + + RyanJson_t item = NULL; + RyanJsonObjectForEach(pJson, item) + { + // 递归调用 + if (RyanJsonTrue != RyanJsonFuzzerTestReplace(item, size)) { return RyanJsonFalse; } + } + + // 按 key 替换(仅 Object) + do + { + if (RyanJsonFalse == RyanJsonIsObject(pJson)) { break; } + + // 随机尝试替换 0 / 中间 / 结尾 子节点 + uint32_t jsonSize = RyanJsonGetSize(pJson); + if (0 == jsonSize) { break; } + + uint32_t index = RyanJsonFuzzerNextRand() % jsonSize; + RyanJson_t targetItem = RyanJsonGetObjectByIndex(pJson, index); + if (NULL == targetItem || RyanJsonTrue != RyanJsonIsKey(targetItem)) { break; } + + RyanJson_t newItem = NULL; + uint32_t choice = RyanJsonFuzzerNextRand() % 3; + + // 策略一:使用相同 key(覆盖 key 相等分支) + if (0 == choice) { newItem = RyanJsonFuzzerCreateRandomNodeWithKey(pJson, RyanJsonGetKey(targetItem)); } + // 策略二:使用不同 key(覆盖 key 不相等分支,触发 ChangeKey) + else if (1 == choice) { newItem = RyanJsonFuzzerCreateRandomNodeWithKey(pJson, "diff_key_random"); } + // 策略三:不带 key(覆盖无 key 分支,触发 CreateItem) + else + { + newItem = RyanJsonFuzzerCreateRandomNode(pJson); + } + + // 尝试替换 + if (RyanJsonFalse == RyanJsonReplaceByKey(pJson, RyanJsonGetKey(targetItem), newItem)) + { + // 替换失败(可能是内存模拟失败),需手动释放 newItem 防止泄漏 + if (NULL != newItem) { RyanJsonDelete(newItem); } + } + } while (0); + + // 按 index 替换(Array/Object) + { + uint32_t idx = 0; + uint32_t jsonSize = RyanJsonGetSize(pJson); + if (0 != jsonSize) { idx = size % jsonSize; } // 随机选择一个有效 index + + RyanJson_t newItem = RyanJsonFuzzerCreateRandomNode(pJson); + + // 执行替换 + if (RyanJsonFalse == RyanJsonReplaceByIndex(pJson, idx, newItem)) + { + // 替换失败,手动释放 + if (NULL != newItem) { RyanJsonDelete(newItem); } + } + } + + return RyanJsonTrue; +} diff --git a/test/fuzzer/entry.c b/test/fuzzer/entry.c new file mode 100644 index 0000000..6a05187 --- /dev/null +++ b/test/fuzzer/entry.c @@ -0,0 +1,64 @@ +#include "RyanJsonFuzzer.h" +#include + +/** + * @brief LLVM LibFuzzer 主入口 + * + * 每轮 Fuzz 迭代都会调用该函数。 + * + * 主要流程: + * - 初始化 Fuzzer 状态与随机源。 + * - 检查首字节是否为 `0xFF`,决定执行 API 序列模式或解析模式。 + * - 注册内存 Hook,确保测试过程中资源可控并可回收。 + */ +int LLVMFuzzerTestOneInput(const char *data, uint32_t size) +{ + // 初始化覆盖率/随机状态 + uint8_t magicByte = 0; + if (size > 0) { magicByte = (uint8_t)data[0]; } + if (0xFF == magicByte) { RyanJsonFuzzerInit((const uint8_t *)data + 1, size - 1); } + + if (0 == g_fuzzerState.seed) { RyanJsonFuzzerInit((const uint8_t *)data, size); } + g_fuzzerState.isEnableMemFail = true; + + assert(RyanJsonFalse == RyanJsonInitHooks(NULL, RyanJsonFuzzerFree, RyanJsonFuzzerRealloc)); + assert(RyanJsonFalse == RyanJsonInitHooks(RyanJsonFuzzerMalloc, NULL, RyanJsonFuzzerRealloc)); + assert(RyanJsonFalse == RyanJsonInitHooks(NULL, NULL, NULL)); + + assert(RyanJsonTrue == RyanJsonInitHooks(RyanJsonFuzzerMalloc, RyanJsonFuzzerFree, 0 != size % 2 ? NULL : RyanJsonFuzzerRealloc)); + + assert(NULL == RyanJsonParseOptions(NULL, 100, RyanJsonFalse, NULL)); + assert(NULL == RyanJsonParseOptions(data, 0, RyanJsonFalse, NULL)); + + const char *parseEndPtr = NULL; + RyanJson_t pJson = RyanJsonParseOptions(data, size, 0 != size % 3 ? RyanJsonTrue : RyanJsonFalse, &parseEndPtr); + if (NULL != pJson) + { + RyanJsonFuzzerTestMinify(data, size); + RyanJsonFuzzerTestParse(pJson, data, size); + RyanJsonFuzzerTestGet(pJson, size); + + RyanJsonFuzzerTestDuplicate(pJson); + RyanJsonCheckCode(RyanJsonFuzzerTestModify(pJson, size), { goto exit__; }); + RyanJsonCheckCode(RyanJsonFuzzerTestCreate(pJson, size), { goto exit__; }); + RyanJsonCheckCode(RyanJsonFuzzerTestDelete(pJson, size), { goto exit__; }); + RyanJsonCheckCode(RyanJsonFuzzerTestReplace(pJson, size), { goto exit__; }); + + // 测试分离 + { + g_fuzzerState.isEnableMemFail = false; + RyanJson_t pJson2 = RyanJsonDuplicate(pJson); + g_fuzzerState.isEnableMemFail = true; + RyanJsonDelete(pJson); + + RyanJsonFuzzerTestDetach(pJson2, size); + RyanJsonDelete(pJson2); + } + } + + return 0; + +exit__: + RyanJsonDelete(pJson); + return 0; +} diff --git a/test/fuzzer/include/RyanJsonFuzzer.h b/test/fuzzer/include/RyanJsonFuzzer.h new file mode 100644 index 0000000..14434dd --- /dev/null +++ b/test/fuzzer/include/RyanJsonFuzzer.h @@ -0,0 +1,126 @@ +#ifndef RyanJsonFuzzer_h +#define RyanJsonFuzzer_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include "testCommon.h" +#include "RyanJsonInternal.h" +#include +#include +#include + +/** + * @brief RyanJson Fuzzer 架构说明 + * + * 架构分层: + * - Driver(`fuzzerDriver`):负责状态管理、伪随机数生成和确定性控制。 + * - Runner(`entry.c`):作为 `LLVMFuzzerTestOneInput` 入口,负责初始化、模式分发和清理。 + * - Case(`cases/`):承载具体测试逻辑,覆盖 Parse/Create/Modify/Structure 等模块。 + * + * 确定性与可复现性: + * - 以输入数据为种子驱动 PRNG,保证同一输入触发同一随机路径。 + * - `FuzzerState` 记录当前种子、输入缓冲区和读取位置。 + * - 通过 PRNG 控制内存失败注入,稳定覆盖 OOM 路径。 + * + * 分发策略: + * - 若首字节为 `0xFF`,进入 API 序列模式(预留扩展)。 + * - 其他输入进入解析模式,执行 Parse/Minify 与后续结构化变异测试。 + * - 该策略对普通 Json corpus 透明兼容。 + * + * 目录说明: + * - `runner/`:入口调度代码。 + * - `cases/`:各类测试用例。 + * - `utils/`:内存钩子、生成器、驱动实现。 + * - `include/`:公共声明。 + */ + +/** + * @brief Fuzzer 运行状态上下文 + * 用于在各个测试用例间传递状态,保证无全局副作用,实现可重入和确定性。 + */ +typedef struct +{ + uint32_t seed; // PRNG 种子,由 Input Data 初始化 + const uint8_t *data; // 原始输入数据指针 + size_t size; // 原始输入数据大小 + size_t pos; // 当前读取位置 + bool isEnableMemFail; // 是否启用内存分配失败 +} RyanJsonFuzzerState; + +/** + * @brief 驱动层接口 + * 定义在 utils/fuzzerDriver.c + */ +extern void RyanJsonFuzzerInit(const uint8_t *data, size_t size); +extern uint32_t RyanJsonFuzzerNextRand(); +extern RyanJsonBool_e RyanJsonFuzzerShouldFail(uint32_t probability); + +/** + * @brief 公共宏定义 + */ +#define RyanJsonCheckGotoExit(EX) \ + RyanJsonCheckCode(EX, { \ + result = RyanJsonFalse; \ + goto exit__; \ + }) + +#define fuzzTestWithMemFail(func) \ + do \ + { \ + uint32_t lastIsEnableMemFail = g_fuzzerState.isEnableMemFail; \ + g_fuzzerState.isEnableMemFail = false; \ + func; \ + g_fuzzerState.isEnableMemFail = true; \ + } while (0) + +/** + * @brief 全局变量声明 + */ +extern RyanJsonFuzzerState g_fuzzerState; + +/** + * @brief 内存钩子函数 + */ +extern void *RyanJsonFuzzerMalloc(size_t size); +extern void RyanJsonFuzzerFree(void *block); +extern void *RyanJsonFuzzerRealloc(void *block, size_t size); + +/** + * @brief 解析与基础功能测试 + * 覆盖 Json 解析、压缩和基础打印功能。 + */ +extern RyanJsonBool_e RyanJsonFuzzerTestParse(RyanJson_t pJson, const char *data, uint32_t size); +extern RyanJsonBool_e RyanJsonFuzzerTestMinify(const char *data, uint32_t size); + +/** + * @brief 深度复制与比较测试 + * 验证复制结果与原对象的一致性。 + */ +extern RyanJsonBool_e RyanJsonFuzzerTestDuplicate(RyanJson_t pJson); + +/** + * @brief 结构修改与访问测试 + * 测试对 Json 结构的修改(改值、改 key)以及数据访问安全性。 + */ +extern RyanJsonBool_e RyanJsonFuzzerTestModify(RyanJson_t pJson, uint32_t size); +extern RyanJsonBool_e RyanJsonFuzzerTestGet(RyanJson_t pJson, uint32_t size); +extern RyanJsonBool_e RyanJsonFuzzerVerifyGet(RyanJson_t lastJson, RyanJson_t pJson, uint32_t index, uint32_t size); + +/** + * @brief 结构变异与增删测试 + * 测试节点的创建、替换、分离和删除,模拟复杂的 DOM 操作序列。 + */ +extern RyanJson_t RyanJsonFuzzerCreateRandomNode(RyanJson_t pJson); +extern RyanJson_t RyanJsonFuzzerCreateRandomNodeWithKey(RyanJson_t pJson, const char *key); +extern RyanJsonBool_e RyanJsonFuzzerTestCreate(RyanJson_t pJson, uint32_t size); +extern RyanJsonBool_e RyanJsonFuzzerTestReplace(RyanJson_t pJson, uint32_t size); +extern RyanJsonBool_e RyanJsonFuzzerTestDetach(RyanJson_t pJson, uint32_t size); +extern RyanJsonBool_e RyanJsonFuzzerTestDelete(RyanJson_t pJson, uint32_t size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/test/fuzzer/utils/fuzzerDriver.c b/test/fuzzer/utils/fuzzerDriver.c new file mode 100644 index 0000000..8c9d9a3 --- /dev/null +++ b/test/fuzzer/utils/fuzzerDriver.c @@ -0,0 +1,69 @@ +#include "RyanJsonFuzzer.h" +#include + +#include "RyanJsonFuzzer.h" +#include + +/** + * @brief 初始化 Fuzzer 状态 + * + * 使用输入数据的前 4 个字节初始化 PRNG 种子,确保 Fuzzing 的确定性。 + * + * @param state Fuzzer 状态指针 + * @param data 原始输入数据 + * @param size 输入数据大小 + */ +void RyanJsonFuzzerInit(const uint8_t *data, size_t size) +{ + g_fuzzerState.data = data; + g_fuzzerState.size = size; + g_fuzzerState.pos = 0; + g_fuzzerState.isEnableMemFail = true; + + if (0 == g_fuzzerState.seed) { g_fuzzerState.seed = time(NULL); } + if (size >= 4) + { + uint32_t seed_input; + memcpy(&seed_input, data, 4); + g_fuzzerState.seed ^= seed_input; + } + else + { + for (size_t i = 0; i < size; i++) + { + g_fuzzerState.seed ^= ((uint32_t)data[i] << (i * 8)); + } + } +} + +/** + * @brief 生成下一个伪随机数 (Xorshift32) + * + * 一个极其快速且轻量级的 PRNG,适合 Fuzzer 环境。 + * + * @return uint32_t 随机数 + */ +uint32_t RyanJsonFuzzerNextRand() +{ + uint32_t x = g_fuzzerState.seed; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + g_fuzzerState.seed = x; + return x; +} + +/** + * @brief 决定是否应该触发失败 + * + * 用于模拟内存分配失败等随机错误路径。 + * + * @param probability 概率倒数 (例如 100 表示 1/100 的概率) + * @return RyanJsonBool_e RyanJsonTrue 表示应该触发失败 + */ +RyanJsonBool_e RyanJsonFuzzerShouldFail(uint32_t probability) +{ + if (false == g_fuzzerState.isEnableMemFail) { return RyanJsonFalse; } + if (0 == probability) { return RyanJsonTrue; } + return (0 == RyanJsonFuzzerNextRand() % probability) ? RyanJsonTrue : RyanJsonFalse; +} diff --git a/test/fuzzer/utils/fuzzerGenerator.c b/test/fuzzer/utils/fuzzerGenerator.c new file mode 100644 index 0000000..a4d1592 --- /dev/null +++ b/test/fuzzer/utils/fuzzerGenerator.c @@ -0,0 +1,45 @@ +#include "RyanJson.h" +#include "RyanJsonFuzzer.h" + +/** + * @brief 随机节点生成器 + * + * 用于生成随机 RyanJson 节点,服务于 Create/Insert/Replace 等测试场景。 + * 会覆盖基础类型(Bool/Null/Int/Double/String)与复合类型(Object/Array)。 + * + * @param pJson 父节点上下文(当前未使用,保留扩展位) + * @return RyanJson_t 生成的新节点 + */ +RyanJson_t RyanJsonFuzzerCreateRandomNodeWithKey(RyanJson_t pJson, const char *key) +{ + (void)pJson; + RyanJson_t item = NULL; + uint32_t randomVal = RyanJsonFuzzerNextRand(); + + switch (randomVal % 8) + { + case 0: item = RyanJsonCreateBool(key, RyanJsonTrue); break; + case 1: item = RyanJsonCreateBool(key, RyanJsonFalse); break; + case 2: item = RyanJsonCreateNull(key); break; + case 3: item = RyanJsonCreateInt(key, (int32_t)RyanJsonFuzzerNextRand()); break; + case 4: item = RyanJsonCreateDouble(key, 1.0 * RyanJsonFuzzerNextRand() / 1000.0); break; + case 5: + item = RyanJsonCreateString(key, "random_string"); // 当前使用固定字符串,保证可重复性 + break; + case 6: + item = RyanJsonCreateObject(); + if (key) { RyanJsonChangeKey(item, key); } // CreateObject 不接收 key 参数 + break; + case 7: + item = RyanJsonCreateArray(); + if (key) { RyanJsonChangeKey(item, key); } // CreateArray 不接收 key 参数 + break; + } + + return item; +} + +RyanJson_t RyanJsonFuzzerCreateRandomNode(RyanJson_t pJson) +{ + return RyanJsonFuzzerCreateRandomNodeWithKey(pJson, NULL); +} diff --git a/test/fuzzer/utils/fuzzerMemory.c b/test/fuzzer/utils/fuzzerMemory.c new file mode 100644 index 0000000..9a55c18 --- /dev/null +++ b/test/fuzzer/utils/fuzzerMemory.c @@ -0,0 +1,21 @@ +#include "RyanJsonFuzzer.h" + +// Defined in entry.c +RyanJsonFuzzerState g_fuzzerState = {0}; + +void *RyanJsonFuzzerMalloc(size_t size) +{ + if (g_fuzzerState.isEnableMemFail && RyanJsonFuzzerShouldFail(598)) { return NULL; } + return (char *)malloc(size); +} + +void RyanJsonFuzzerFree(void *block) +{ + free(block); +} + +void *RyanJsonFuzzerRealloc(void *block, size_t size) +{ + if (g_fuzzerState.isEnableMemFail && RyanJsonFuzzerShouldFail(508)) { return NULL; } + return (char *)realloc(block, size); +} diff --git a/test/qemu/common/FreeRTOSConfig.h b/test/qemu/common/FreeRTOSConfig.h new file mode 100644 index 0000000..a514069 --- /dev/null +++ b/test/qemu/common/FreeRTOSConfig.h @@ -0,0 +1,96 @@ +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +#include +#include + +#include "qemuPlatform.h" + +#define configASSERT(x) \ + do \ + { \ + if ((x) == 0) { qemuAssertFailed(__FILE__, __LINE__); } \ + } while (0) + +#define configUSE_PREEMPTION 1 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configUSE_TIME_SLICING 1 +#define configUSE_TICK_HOOK 0 +#define configUSE_IDLE_HOOK 0 +#define configCHECK_HANDLER_INSTALLATION 0 +#define configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 1 + +#define configCPU_CLOCK_HZ 25000000UL +#define configTICK_RATE_HZ 1000UL +#define configMAX_PRIORITIES 16 +#define configMINIMAL_STACK_SIZE 256U +#define configMAX_TASK_NAME_LEN 32 +#define configIDLE_SHOULD_YIELD 1 +#define configRECORD_STACK_HIGH_ADDRESS 1 +#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITS + +#define configUSE_TASK_NOTIFICATIONS 1 +#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configUSE_QUEUE_SETS 1 +#define configUSE_APPLICATION_TASK_TAG 1 +#define configQUEUE_REGISTRY_SIZE 16 +#define configENABLE_BACKWARD_COMPATIBILITY 0 +#define configUSE_MINI_LIST_ITEM 0 +#define configSTACK_DEPTH_TYPE size_t +#define configMESSAGE_BUFFER_LENGTH_TYPE size_t + +#define configCHECK_FOR_STACK_OVERFLOW 2 +#define configUSE_MALLOC_FAILED_HOOK 1 +#define configUSE_SB_COMPLETED_CALLBACK 1 +#define configUSE_TRACE_FACILITY 1 +#define configUSE_STATS_FORMATTING_FUNCTIONS 1 +#define configGENERATE_RUN_TIME_STATS 0 +#define configSTATS_BUFFER_MAX_LENGTH 0xFFFFU + +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configAPPLICATION_ALLOCATED_HEAP 1 +#define configTOTAL_HEAP_SIZE (2U * 1024U * 1024U) + +#define configUSE_CO_ROUTINES 0 +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) +#define configTIMER_QUEUE_LENGTH 20 +#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2U) + +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 +#define configUSE_POSIX_ERRNO 1 + +#define configPRIO_BITS 3 +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 7 +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 + +#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) +#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) + +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskDelayUntil 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_xTaskGetIdleTaskHandle 1 +#define INCLUDE_eTaskGetState 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_uxTaskGetStackHighWaterMark2 1 +#define INCLUDE_xTaskGetHandle 1 +#define INCLUDE_xTaskAbortDelay 1 +#define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskResumeFromISR 1 +#define INCLUDE_xSemaphoreGetMutexHolder 1 + +#define vPortSVCHandler SVC_Handler +#define xPortPendSVHandler PendSV_Handler +#define xPortSysTickHandler SysTick_Handler + +#endif diff --git a/test/qemu/platform/linkerMps2An386.ld b/test/qemu/platform/linkerMps2An386.ld new file mode 100644 index 0000000..f25f6d6 --- /dev/null +++ b/test/qemu/platform/linkerMps2An386.ld @@ -0,0 +1,102 @@ +ENTRY(Reset_Handler) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 4M + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 4M + RAM2 (rwx) : ORIGIN = 0x21000000, LENGTH = 2M +} + +/* Keep stack at the RAM top to avoid overlapping the large localbase test .bss region. */ +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) + . = ALIGN(4); + } > FLASH + + .text : + { + . = ALIGN(4); + *(.text*) + *(.rodata*) + *(.glue_7) + *(.glue_7t) + *(.eh_frame) + KEEP(*(.init)) + KEEP(*(.fini)) + . = ALIGN(4); + _etext = .; + } > FLASH + + .ARM.extab : + { + . = ALIGN(4); + *(.ARM.extab* .gnu.linkonce.armextab.*) + . = ALIGN(4); + } > FLASH + + .ARM.exidx : + { + . = ALIGN(4); + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + . = ALIGN(4); + } > FLASH + + _sidata = LOADADDR(.data); + + .data : + { + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : + { + . = ALIGN(8); + _sbss = .; + *(.bss*) + *(COMMON) + . = ALIGN(8); + _ebss = .; + } > RAM + + .freertosHeap (NOLOAD) : + { + . = ALIGN(8); + __freertosHeapStart = .; + KEEP(*(.freertosHeap)) + KEEP(*(.freertosHeap*)) + . = ALIGN(8); + __freertosHeapEnd = .; + } > RAM2 + + .heap (NOLOAD) : + { + . = ALIGN(8); + _end = .; + end = .; + __end__ = .; + __HeapBase = .; + __heap_start__ = .; + . = ORIGIN(RAM) + LENGTH(RAM); + __HeapLimit = .; + __heap_end__ = .; + } > RAM + + ASSERT(__freertosHeapEnd <= (ORIGIN(RAM2) + LENGTH(RAM2)), "freertosHeap overflows RAM2") + + /DISCARD/ : + { + *(.note*) + } +} diff --git a/test/qemu/platform/qemuFault.c b/test/qemu/platform/qemuFault.c new file mode 100644 index 0000000..f3339a6 --- /dev/null +++ b/test/qemu/platform/qemuFault.c @@ -0,0 +1,104 @@ +#include + +#include "qemuPlatform.h" + +#define qemuScbCfsrReg (*(volatile uint32_t *)0xE000ED28UL) +#define qemuScbHfsrReg (*(volatile uint32_t *)0xE000ED2CUL) +#define qemuScbMmfarReg (*(volatile uint32_t *)0xE000ED34UL) +#define qemuScbBfarReg (*(volatile uint32_t *)0xE000ED38UL) +#define qemuSsramStart (0x20000000UL) +#define qemuSsramEnd (0x20400000UL) +#define qemuPsramStart (0x21000000UL) +#define qemuPsramEnd (0x21200000UL) +#define qemuStackFrameWords (8UL) + +extern uint32_t _estack; + +static bool qemuIsAddressInRange(uintptr_t address, uintptr_t rangeStart, uintptr_t rangeEnd) +{ + return (address >= rangeStart) && (address < rangeEnd); +} + +static bool qemuIsValidStackedFrame(const uint32_t *stackPtr) +{ + const uintptr_t address = (uintptr_t)stackPtr; + const uintptr_t frameEnd = address + (qemuStackFrameWords * sizeof(uint32_t)); + bool inSsram = false; + bool inPsram = false; + + if (frameEnd < address) { return false; } + + inSsram = qemuIsAddressInRange(address, qemuSsramStart, qemuSsramEnd) && (frameEnd <= qemuSsramEnd); + inPsram = qemuIsAddressInRange(address, qemuPsramStart, qemuPsramEnd) && (frameEnd <= qemuPsramEnd); + + return (NULL != stackPtr) && (0U == (address & 0x7UL)) && (inSsram || inPsram); +} + +static void qemuDumpFaultContext(const char *tag, uint32_t *stackPtr, uint32_t excReturn) +{ + const uint32_t cfsr = qemuScbCfsrReg; + const uint32_t hfsr = qemuScbHfsrReg; + const uint32_t mmfar = qemuScbMmfarReg; + const uint32_t bfar = qemuScbBfarReg; + + qemuLogf("[QEMU][%s] EXC_RETURN=0x%08lx\n", tag, (unsigned long)excReturn); + qemuLogf("[QEMU][%s] CFSR=0x%08lx HFSR=0x%08lx BFAR=0x%08lx MMFAR=0x%08lx\n", tag, (unsigned long)cfsr, (unsigned long)hfsr, + (unsigned long)bfar, (unsigned long)mmfar); + + if (qemuIsValidStackedFrame(stackPtr)) + { + qemuLogf("[QEMU][%s] stacked_r0=0x%08lx stacked_r1=0x%08lx stacked_r2=0x%08lx stacked_r3=0x%08lx\n", tag, + (unsigned long)stackPtr[0], (unsigned long)stackPtr[1], (unsigned long)stackPtr[2], (unsigned long)stackPtr[3]); + qemuLogf("[QEMU][%s] stacked_r12=0x%08lx stacked_lr=0x%08lx stacked_pc=0x%08lx stacked_xpsr=0x%08lx\n", tag, + (unsigned long)stackPtr[4], (unsigned long)stackPtr[5], (unsigned long)stackPtr[6], (unsigned long)stackPtr[7]); + } + else + { + qemuLogf("[QEMU][%s] stacked_frame invalid: 0x%08lx\n", tag, (unsigned long)(uintptr_t)stackPtr); + } + + if ((cfsr & (1UL << 24)) != 0U) { qemuLogf("[QEMU][%s] CFSR.UNALIGNED=1\n", tag); } + + if (qemuHardfaultWasExpected()) { qemuLogf("[QEMU][RESULT] EXPECTED_UNALIGNED_FAULT cfsr=0x%08lx\n", (unsigned long)cfsr); } +} + +void qemuHardfaultHandlerC(uint32_t *stackPtr, uint32_t excReturn) +{ + bool expectedFault = qemuHardfaultWasExpected(); + qemuDumpFaultContext("HARDFAULT", stackPtr, excReturn); + qemuSetExpectUnalignedFault(false); + + if (expectedFault) { qemuRequestExit(0); } + + qemuRequestExit(1); +} + +__attribute__((naked)) void HardFault_Handler(void) +{ + __asm volatile("tst lr, #4 \n" + "ite eq \n" + "mrseq r0, msp \n" + "mrsne r0, psp \n" + "mov r1, lr \n" + "ldr r2, =_estack \n" + "msr msp, r2 \n" + "b qemuHardfaultHandlerC \n"); +} + +void UsageFault_Handler(void) +{ + qemuDumpFaultContext("USAGEFAULT", NULL, 0U); + qemuRequestExit(1); +} + +void BusFault_Handler(void) +{ + qemuDumpFaultContext("BUSFAULT", NULL, 0U); + qemuRequestExit(1); +} + +void MemManage_Handler(void) +{ + qemuDumpFaultContext("MEMFAULT", NULL, 0U); + qemuRequestExit(1); +} diff --git a/test/qemu/platform/qemuFreertosHeap.c b/test/qemu/platform/qemuFreertosHeap.c new file mode 100644 index 0000000..35faeb9 --- /dev/null +++ b/test/qemu/platform/qemuFreertosHeap.c @@ -0,0 +1,6 @@ +#include + +#include "FreeRTOS.h" + +/* Keep heap_4 outside the main SRAM window to reduce pressure from large localbase test data. */ +__attribute__((section(".freertosHeap"), aligned(portBYTE_ALIGNMENT))) uint8_t ucHeap[configTOTAL_HEAP_SIZE]; diff --git a/test/qemu/platform/qemuPlatform.c b/test/qemu/platform/qemuPlatform.c new file mode 100644 index 0000000..3166797 --- /dev/null +++ b/test/qemu/platform/qemuPlatform.c @@ -0,0 +1,102 @@ +#include "qemuPlatform.h" + +#include + +#define qemuUart0Base (0x40004000UL) +#define qemuUartDataReg (*(volatile uint32_t *)(qemuUart0Base + 0x000U)) +#define qemuUartStateReg (*(volatile uint32_t *)(qemuUart0Base + 0x004U)) +#define qemuUartCtrlReg (*(volatile uint32_t *)(qemuUart0Base + 0x008U)) +#define qemuUartBauddivReg (*(volatile uint32_t *)(qemuUart0Base + 0x010U)) +#define qemuUartStateTxFull (1UL << 0) +#define qemuUartCtrlTxEnable (1UL << 0) +#define qemuUartDefaultBauddiv (16UL) +#define qemuUartDrainSpinCount (200000UL) + +static volatile uint32_t gExpectUnalignedFault = 0U; +extern void _exit(int status); + +void qemuPlatformInit(void) +{ + /* CMSDK UART: enable TX and keep a conservative divider for deterministic output. */ + qemuUartBauddivReg = qemuUartDefaultBauddiv; + qemuUartCtrlReg = qemuUartCtrlTxEnable; +} + +void qemuUartPutc(char ch) +{ + for (uint32_t spin = 0U; spin < 1000000U; ++spin) + { + if ((qemuUartStateReg & qemuUartStateTxFull) == 0U) { break; } + } + + qemuUartDataReg = (uint32_t)(uint8_t)ch; +} + +void qemuUartWrite(const char *str) +{ + if (NULL == str) { return; } + + while ('\0' != *str) + { + if ('\n' == *str) { qemuUartPutc('\r'); } + qemuUartPutc(*str); + ++str; + } +} + +void qemuWaitUartDrain(void) +{ + for (volatile uint32_t spin = 0U; spin < qemuUartDrainSpinCount; ++spin) + { + __asm volatile("nop"); + } +} + +void qemuVlogf(const char *fmt, va_list args) +{ + char buffer[256]; + + if (NULL == fmt) { return; } + + (void)vsnprintf(buffer, sizeof(buffer), fmt, args); + qemuUartWrite(buffer); +} + +void qemuLogf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + qemuVlogf(fmt, args); + va_end(args); +} + +void qemuAssertFailed(const char *file, int line) +{ + qemuLogf("[QEMU][ASSERT] %s:%d\n", (NULL != file) ? file : "", line); + qemuRequestExit(1); +} + +void qemuRequestExit(int32_t status) +{ + qemuWaitUartDrain(); + _exit((int)status); + qemuPanicLoop(); +} + +void qemuPanicLoop(void) +{ + for (;;) + { + __asm volatile("wfi"); + } +} + +bool qemuHardfaultWasExpected(void) +{ + return (0U != gExpectUnalignedFault); +} + +void qemuSetExpectUnalignedFault(bool expect) +{ + gExpectUnalignedFault = expect ? 1U : 0U; +} diff --git a/test/qemu/platform/qemuPlatform.h b/test/qemu/platform/qemuPlatform.h new file mode 100644 index 0000000..04a88b0 --- /dev/null +++ b/test/qemu/platform/qemuPlatform.h @@ -0,0 +1,36 @@ +#ifndef ryanJsonQemuPlatformH +#define ryanJsonQemuPlatformH + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +void qemuPlatformInit(void); +void qemuUartPutc(char ch); +void qemuUartWrite(const char *str); +void qemuWaitUartDrain(void); +void qemuLogf(const char *fmt, ...); +void qemuVlogf(const char *fmt, va_list args); +void qemuAssertFailed(const char *file, int line); +void qemuRequestExit(int32_t status) __attribute__((noreturn)); +void qemuPanicLoop(void) __attribute__((noreturn)); + +bool qemuHardfaultWasExpected(void); +void qemuSetExpectUnalignedFault(bool expect); + +#define qemuAssert(expr) \ + do \ + { \ + if (!(expr)) { qemuAssertFailed(__FILE__, __LINE__); } \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/test/qemu/platform/qemuStartup.c b/test/qemu/platform/qemuStartup.c new file mode 100644 index 0000000..dcec1fd --- /dev/null +++ b/test/qemu/platform/qemuStartup.c @@ -0,0 +1,105 @@ +#include + +#include "qemuPlatform.h" + +extern int main(void); +extern void __libc_init_array(void); + +extern uint32_t _sidata; +extern uint32_t _sdata; +extern uint32_t _edata; +extern uint32_t _sbss; +extern uint32_t _ebss; +extern uint32_t _estack; + +void _init(void) +{ +} + +void _fini(void) +{ +} + +void Reset_Handler(void); +void Default_Handler(void); + +void NMI_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void MemManage_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void BusFault_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void UsageFault_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void SVC_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void DebugMon_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void PendSV_Handler(void) __attribute__((weak, alias("Default_Handler"))); +void SysTick_Handler(void) __attribute__((weak, alias("Default_Handler"))); + +extern void HardFault_Handler(void); + +__attribute__((section(".isr_vector"))) const void *const gQemuVectors[] = { + &_estack, + Reset_Handler, + NMI_Handler, + HardFault_Handler, + MemManage_Handler, + BusFault_Handler, + UsageFault_Handler, + 0, + 0, + 0, + 0, + SVC_Handler, + DebugMon_Handler, + 0, + PendSV_Handler, + SysTick_Handler // +}; + +static void qemuCopyDataAndBss(void) +{ + uint32_t *src = &_sidata; + uint32_t *dst = &_sdata; + + while (dst < &_edata) + { + *dst++ = *src++; + } + + dst = &_sbss; + while (dst < &_ebss) + { + *dst++ = 0U; + } +} + +void Reset_Handler(void) +{ + volatile uint32_t *const scbVtor = (volatile uint32_t *)0xE000ED08UL; + volatile uint32_t *const scbCcr = (volatile uint32_t *)0xE000ED14UL; + volatile uint32_t *const scbShcsr = (volatile uint32_t *)0xE000ED24UL; + + qemuCopyDataAndBss(); + *scbVtor = (uint32_t)(uintptr_t)gQemuVectors; + + qemuPlatformInit(); + + /* Route configurable faults to dedicated handlers for clearer diagnostics. */ + *scbShcsr |= ((1UL << 16) | (1UL << 17) | (1UL << 18)); + + /* Force unaligned accesses to trap so QEMU can validate hardware semantics. */ + *scbCcr |= (1UL << 3); + + qemuLogf("[QEMU][BOOT] reset entered\n"); + qemuLogf("[QEMU][BOOT] SCB->CCR.UNALIGN_TRP=1\n"); + + __libc_init_array(); + + (void)main(); + + qemuLogf("[QEMU][BOOT] main returned unexpectedly\n"); + qemuPanicLoop(); +} + +void Default_Handler(void) +{ + qemuLogf("[QEMU][FAULT] unexpected exception\n"); + qemuPanicLoop(); +} diff --git a/test/qemu/platform/qemuSyscalls.c b/test/qemu/platform/qemuSyscalls.c new file mode 100644 index 0000000..ccbd1c5 --- /dev/null +++ b/test/qemu/platform/qemuSyscalls.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include + +#include "qemuPlatform.h" + +extern uint8_t _end; +extern uint8_t _estack; + +#define qemuSemihostingOpSysExitExtended (0x20U) +#define qemuSemihostingReasonAppExit (0x20026U) + +typedef struct +{ + uint32_t reason; + uint32_t subCode; +} qemuSemihostingExitArgs_t; + +static void qemuSemihostingExit(int status) +{ + qemuSemihostingExitArgs_t exitArgs = {qemuSemihostingReasonAppExit, (uint32_t)status}; +#if defined(__arm__) || defined(__thumb__) + register uint32_t opReg __asm("r0") = qemuSemihostingOpSysExitExtended; + register qemuSemihostingExitArgs_t *argReg __asm("r1") = &exitArgs; + __asm volatile("bkpt 0xAB" : : "r"(opReg), "r"(argReg) : "memory"); +#else + (void)exitArgs; +#endif +} + +caddr_t _sbrk(int incr) +{ + static uint8_t *heap = &_end; + uint8_t *const prevHeap = heap; + uint8_t *const nextHeap = heap + incr; + + if (nextHeap >= &_estack) + { + errno = ENOMEM; + return (caddr_t)-1; + } + + heap = nextHeap; + return (caddr_t)prevHeap; +} + +int _write(int fd, const void *buf, size_t count) +{ + const char *ptr = (const char *)buf; + + if ((1 != fd && 2 != fd) || NULL == ptr) + { + errno = EBADF; + return -1; + } + + for (size_t i = 0U; i < count; ++i) + { + qemuUartPutc(ptr[i]); + } + + return (int)count; +} + +int _read(int fd, void *buf, size_t count) +{ + (void)fd; + (void)buf; + (void)count; + errno = ENOSYS; + return -1; +} + +int _close(int fd) +{ + (void)fd; + return 0; +} + +int _fstat(int fd, struct stat *st) +{ + (void)fd; + + if (NULL == st) + { + errno = EFAULT; + return -1; + } + + st->st_mode = S_IFCHR; + return 0; +} + +int _isatty(int fd) +{ + return (1 == fd || 2 == fd) ? 1 : 0; +} + +off_t _lseek(int fd, off_t offset, int whence) +{ + (void)fd; + (void)offset; + (void)whence; + return 0; +} + +int _kill(int pid, int sig) +{ + (void)pid; + (void)sig; + errno = EINVAL; + return -1; +} + +int _getpid(void) +{ + return 1; +} + +void _exit(int status) +{ + qemuLogf("[QEMU][EXIT] status=%d\n", status); + qemuWaitUartDrain(); + qemuSemihostingExit(status); + qemuPanicLoop(); +} diff --git a/test/unityTest/cases/RFC8259/testRfc8259.c b/test/unityTest/cases/RFC8259/testRfc8259.c new file mode 100644 index 0000000..c8c12cf --- /dev/null +++ b/test/unityTest/cases/RFC8259/testRfc8259.c @@ -0,0 +1,394 @@ +#include "testCommon.h" +#include "valloc.h" + +#if defined(RyanJsonTestPlatformQemu) +void testRfc8259Runner(void) +{ + UnitySetTestFile(__FILE__); +} +#else + +#include +#include + +#define PrintfStrCmpEnable + +#ifndef RyanJsonProjectRootPath +#define RyanJsonProjectRootPath "." +#endif + +#define RFC8259TestFilePath RyanJsonProjectRootPath "/test/data/rfc8259" +#define RFC8259TotalCaseCount 322U +#define RFC8259RyanJsonPassCountStrict 320U +#define RFC8259RyanJsonPassCountNonStrict 322U + +typedef RyanJsonBool_e (*jsonParseData)(char *fileName, char *data, uint32_t len); + +typedef struct +{ + char **fileNames; + uint32_t fileCount; +} rfc8259FileList_t; + +static void freeRfc8259FileList(rfc8259FileList_t *fileList) +{ + if (NULL == fileList) { return; } + if (NULL != fileList->fileNames) + { + for (uint32_t i = 0U; i < fileList->fileCount; ++i) + { + free(fileList->fileNames[i]); + } + free(fileList->fileNames); + } + fileList->fileNames = NULL; + fileList->fileCount = 0U; +} + +static RyanJsonBool_e hasJsonSuffix(const char *fileName) +{ + size_t fileNameLen = 0U; + static const size_t jsonSuffixLen = sizeof(".json") - 1U; + + if (NULL == fileName) { return RyanJsonFalse; } + fileNameLen = strlen(fileName); + if (fileNameLen < jsonSuffixLen) { return RyanJsonFalse; } + + return (0 == strcmp(fileName + fileNameLen - jsonSuffixLen, ".json")) ? RyanJsonTrue : RyanJsonFalse; +} + +static int compareFileNameAsc(const void *left, const void *right) +{ + const char *leftName = *(const char *const *)left; + const char *rightName = *(const char *const *)right; + if (NULL == leftName && NULL == rightName) { return 0; } + if (NULL == leftName) { return -1; } + if (NULL == rightName) { return 1; } + return strcmp(leftName, rightName); +} + +static RyanJsonBool_e collectRfc8259FileList(const char *path, rfc8259FileList_t *fileListOut) +{ + DIR *dir = NULL; + struct dirent *entry = NULL; + int32_t scanErrno = 0; + uint32_t fileCapacity = 0U; + rfc8259FileList_t fileList = {NULL, 0U}; + + if (NULL == path || NULL == fileListOut) { return RyanJsonFalse; } + + errno = 0; + dir = opendir(path); + if (NULL == dir) + { + (void)testLog("打开 RFC8259 目录失败: %s\n", path); + return RyanJsonFalse; + } + + while (NULL != (entry = readdir(dir))) + { + const char *fileName = entry->d_name; + char *nameCopy = NULL; + + if (NULL == fileName) { continue; } + if (0 == strcmp(fileName, ".") || 0 == strcmp(fileName, "..")) { continue; } + if (RyanJsonTrue != hasJsonSuffix(fileName)) { continue; } + + if (fileList.fileCount == fileCapacity) + { + uint32_t newCapacity = (0U == fileCapacity) ? 64U : (fileCapacity * 2U); + char **newFileNames = (char **)realloc(fileList.fileNames, (size_t)newCapacity * sizeof(char *)); + if (NULL == newFileNames) + { + (void)closedir(dir); + freeRfc8259FileList(&fileList); + return RyanJsonFalse; + } + fileList.fileNames = newFileNames; + fileCapacity = newCapacity; + } + + nameCopy = (char *)malloc(strlen(fileName) + 1U); + if (NULL == nameCopy) + { + (void)closedir(dir); + freeRfc8259FileList(&fileList); + return RyanJsonFalse; + } + (void)strcpy(nameCopy, fileName); + fileList.fileNames[fileList.fileCount++] = nameCopy; + } + + scanErrno = errno; + (void)closedir(dir); + if (0 != scanErrno) + { + freeRfc8259FileList(&fileList); + return RyanJsonFalse; + } + + if (fileList.fileCount > 1U) { qsort(fileList.fileNames, fileList.fileCount, sizeof(char *), compareFileNameAsc); } + + *fileListOut = fileList; + return RyanJsonTrue; +} + +/* Read a file, parse, render back, etc. */ +static void testFile(const char *path, jsonParseData jsonParseDataHandle, const char *libName, uint32_t *passCountOut, + uint32_t *totalCountOut) +{ + uint32_t passCount = 0U; + uint32_t usedCount = 0U; + rfc8259FileList_t fileList = {NULL, 0U}; + + if (RyanJsonTrue != collectRfc8259FileList(path, &fileList)) + { + TEST_FAIL_MESSAGE("RFC8259 扫描目录失败"); + return; + } + + // 初始缓冲区 + uint32_t bufferCap = 4096U; + char *data = (char *)malloc(bufferCap); + if (NULL == data) + { + freeRfc8259FileList(&fileList); + TEST_FAIL_MESSAGE("内存分配失败 (RFC8259 数据缓冲区)"); + return; + } + + for (uint32_t i = 0U; i < fileList.fileCount; ++i) + { + char *name = fileList.fileNames[i]; + if (NULL == name || 0 == strlen(name)) { continue; } + + char fullPath[512] = {0}; + int32_t ret = snprintf(fullPath, sizeof(fullPath), "%s/%s", path, name); + if (ret < 0 || ret >= (int32_t)sizeof(fullPath)) { continue; } + + FILE *f = fopen(fullPath, "rb"); + if (NULL == f) + { + (void)testLog("打开文件失败: %s\n", fullPath); + continue; + } + + if (0 != fseek(f, 0, SEEK_END)) + { + (void)fclose(f); + continue; + } + + long fileSize = ftell(f); + if (fileSize < 0) + { + (void)fclose(f); + continue; + } + uint32_t len = (uint32_t)fileSize; + + if (0 != fseek(f, 0, SEEK_SET)) + { + (void)fclose(f); + continue; + } + + // 必要时自动扩容 + if (len + 1 > bufferCap) + { + bufferCap = len + 128; // 预留一点空间 + char *newData = (char *)realloc(data, bufferCap); + if (NULL == newData) + { + (void)fclose(f); + break; + } + data = newData; + } + + if (len != fread(data, 1, len, f)) + { + (void)fclose(f); + continue; + } + data[len] = '\0'; + (void)fclose(f); + + int32_t startUse = unityTestGetUse(); + RyanJsonBool_e status = jsonParseDataHandle(name, data, len); + usedCount++; + + // 判定逻辑优化 + if (0 == strncmp("y_", name, 2)) + { + if (RyanJsonTrue == status) { passCount++; } + else + { + (void)testLog("RFC8259 期望成功但失败: %s, 内容: %s\n", name, data); + } + } + else if (0 == strncmp("n_", name, 2)) + { + if (RyanJsonFalse == status) { passCount++; } + else + { + (void)testLog("RFC8259 期望失败但成功: %s, 内容: %s\n", name, data); + } + } + else if (0 == strncmp("i_", name, 2)) { passCount++; } + + // 内存泄漏检查 + if (startUse != unityTestGetUse()) + { + (void)testLog("RFC8259 内存泄漏于文件: %s\n", name); + TEST_ASSERT_EQUAL_INT_MESSAGE(startUse, unityTestGetUse(), "RFC8259 内存泄漏"); + } + } + + free(data); + freeRfc8259FileList(&fileList); + + if (NULL != passCountOut) { *passCountOut = passCount; } + if (NULL != totalCountOut) { *totalCountOut = usedCount; } + + (void)testLog("\033[1;34m[\033[1;36m%s\033[1;34m] RFC 8259 Json 统计: \033[1;32m(%u/%u)\033[0m\r\n\r\n", libName, passCount, + usedCount); +} + +#include "testRfc8259Util.h" + +static void checkJsonSemanticEquality(char *data, uint32_t len, char *str, uint32_t strLen, uint32_t *errorCount) +{ + if (0 != strcmp(data, str)) + { + if (!RyanJsonValueSemanticEqual(data, len, str, strLen)) + { + (*errorCount)++; + (void)testLog("%d 数据不完全一致 -- 原始: %s -- 序列化: %s\n", *errorCount, data, str); + } + } +} + +static RyanJsonBool_e RyanJsonParseData(char *fileName, char *data, uint32_t len) +{ + if (0 == strcmp(fileName, "n_structure_100000_opening_arrays.json") || 0 == strcmp(fileName, "n_structure_open_array_object.json")) + { + return RyanJsonFalse; + } + + RyanJson_t json = RyanJsonParseOptions(data, len, RyanJsonTrue, NULL); + if (NULL == json) { return RyanJsonFalse; } + +#ifdef PrintfStrCmpEnable + int32_t strLen = 0; + char *str = RyanJsonPrint(json, 60, RyanJsonFalse, &strLen); + if (NULL == str) + { + (void)RyanJsonDelete(json); + return RyanJsonFalse; + } + + RyanJsonMinify(data, (int32_t)len); + static uint32_t semanticErrorCount = 0; + checkJsonSemanticEquality(data, len, str, strLen, &semanticErrorCount); + + RyanJsonFree(str); +#endif + + (void)RyanJsonDelete(json); + return RyanJsonTrue; +} + +static RyanJsonBool_e cJSONParseData(char *fileName, char *data, uint32_t len) +{ + if (0 == strcmp(fileName, "n_structure_100000_opening_arrays.json") || 0 == strcmp(fileName, "n_structure_open_array_object.json")) + { + return RyanJsonFalse; + } + + cJSON *json = cJSON_ParseWithLengthOpts(data, len + sizeof(""), NULL, RyanJsonTrue); + if (NULL == json) { return RyanJsonFalse; } + +#ifdef PrintfStrCmpEnable + char *str = cJSON_PrintBuffered(json, 60, RyanJsonFalse); + if (NULL == str) + { + (void)cJSON_Delete(json); + return RyanJsonFalse; + } + + cJSON_Minify(data); + static uint32_t semanticErrorCount = 0; + checkJsonSemanticEquality(data, len, str, strlen(str), &semanticErrorCount); + + cJSON_free(str); +#endif + + (void)cJSON_Delete(json); + return RyanJsonTrue; +} + +static RyanJsonBool_e yyjsonParseData(char *fileName, char *data, uint32_t len) +{ + if (0 == strcmp(fileName, "n_structure_100000_opening_arrays.json") || 0 == strcmp(fileName, "n_structure_open_array_object.json")) + { + return RyanJsonFalse; + } + + yyjson_doc *doc = yyjson_read(data, len, 0); + if (NULL == doc) { return RyanJsonFalse; } + +#ifdef PrintfStrCmpEnable + char *str = yyjson_write(doc, 0, NULL); + if (NULL == str) + { + (void)yyjson_doc_free(doc); + return RyanJsonFalse; + } + + cJSON_Minify(data); + static uint32_t semanticErrorCount = 0; + checkJsonSemanticEquality(data, len, str, strlen(str), &semanticErrorCount); + + free(str); +#endif + + (void)yyjson_doc_free(doc); + return RyanJsonTrue; +} + +static void testRfc8259RyanJson(void) +{ + cJSON_Hooks hooks = {.malloc_fn = unityTestMalloc, .free_fn = unityTestFree}; + (void)cJSON_InitHooks(&hooks); + + uint32_t passCount = 0; + uint32_t totalCount = 0; + testFile(RFC8259TestFilePath, RyanJsonParseData, "RyanJson", &passCount, &totalCount); + + TEST_ASSERT_EQUAL_UINT32_MESSAGE(RFC8259TotalCaseCount, totalCount, "RFC8259 总用例数应为 322"); + + uint32_t expectedPassCount = + (true == RyanJsonStrictObjectKeyCheck) ? RFC8259RyanJsonPassCountStrict : RFC8259RyanJsonPassCountNonStrict; + TEST_ASSERT_EQUAL_UINT32_MESSAGE(expectedPassCount, passCount, "RyanJson RFC8259 通过数不符合预期(非严格=322,严格=320)"); +} + +static void testRfc8259Yyjson(void) +{ + testFile(RFC8259TestFilePath, yyjsonParseData, "yyjson", NULL, NULL); +} + +static void testRfc8259Cjson(void) +{ + testFile(RFC8259TestFilePath, cJSONParseData, "cJSON", NULL, NULL); +} + +void testRfc8259Runner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testRfc8259RyanJson); + RUN_TEST(testRfc8259Yyjson); + RUN_TEST(testRfc8259Cjson); +} + +#endif diff --git a/test/unityTest/cases/RFC8259/testRfc8259Util.c b/test/unityTest/cases/RFC8259/testRfc8259Util.c new file mode 100644 index 0000000..72371db --- /dev/null +++ b/test/unityTest/cases/RFC8259/testRfc8259Util.c @@ -0,0 +1,278 @@ +#include "RyanJson.h" +#include "testRfc8259Util.h" +#include +#include +#include +#include +#include + +static int32_t hexval(int32_t c) +{ + if (c >= '0' && c <= '9') { return c - '0'; } + c = (c >= 'a' && c <= 'f') ? (c - 'a' + 'A') : c; + if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } + return -1; +} + +static int32_t decode_u4(const char *s, uint16_t *out) +{ + int32_t h0 = hexval(s[0]), h1 = hexval(s[1]), h2 = hexval(s[2]), h3 = hexval(s[3]); + if (h0 < 0 || h1 < 0 || h2 < 0 || h3 < 0) { return 0; } + *out = (uint16_t)((h0 << 12) | (h1 << 8) | (h2 << 4) | h3); + return 1; +} + +int32_t RyanJsonNormalizeString(const char *in, uint32_t inLen, unsigned char **out, uint32_t *outLen) +{ + uint32_t cap = inLen * 4 + 8; + unsigned char *buf = (unsigned char *)malloc(cap); + if (NULL == buf) { return 0; } + uint32_t pos = 0; + + for (uint32_t i = 0; i < inLen; i++) + { + unsigned char ch = (unsigned char)in[i]; + if (ch == '\\') + { + if (i + 1 >= inLen) + { + free(buf); + return 0; + } + unsigned char esc = (unsigned char)in[++i]; + switch (esc) + { + case '\"': buf[pos++] = '\"'; break; + case '\\': buf[pos++] = '\\'; break; + case '/': buf[pos++] = '/'; break; + case 'b': buf[pos++] = '\b'; break; + case 'f': buf[pos++] = '\f'; break; + case 'n': buf[pos++] = '\n'; break; + case 'r': buf[pos++] = '\r'; break; + case 't': buf[pos++] = '\t'; break; + case 'u': { + if (i + 4 >= inLen) + { + free(buf); + return 0; + } + uint16_t u1; + if (!decode_u4(&in[i + 1], &u1)) + { + free(buf); + return 0; + } + i += 4; + if (u1 >= 0xD800 && u1 <= 0xDBFF) + { + if (i + 2 >= inLen || in[i + 1] != '\\' || in[i + 2] != 'u' || i + 6 >= inLen) + { + free(buf); + return 0; + } + i += 2; + uint16_t u2; + if (!decode_u4(&in[i + 1], &u2)) + { + free(buf); + return 0; + } + i += 4; + if (!(u2 >= 0xDC00 && u2 <= 0xDFFF)) + { + free(buf); + return 0; + } + uint32_t cp = 0x10000 + (((uint32_t)(u1 - 0xD800) << 10) | (uint32_t)(u2 - 0xDC00)); + buf[pos++] = (unsigned char)(0xF0 | ((cp >> 18) & 0x07)); + buf[pos++] = (unsigned char)(0x80 | ((cp >> 12) & 0x3F)); + buf[pos++] = (unsigned char)(0x80 | ((cp >> 6) & 0x3F)); + buf[pos++] = (unsigned char)(0x80 | (cp & 0x3F)); + } + else if (u1 >= 0xDC00 && u1 <= 0xDFFF) + { + free(buf); + return 0; + } + else + { + if (u1 <= 0x007F) { buf[pos++] = (unsigned char)u1; } + else if (u1 <= 0x07FF) + { + buf[pos++] = (unsigned char)(0xC0 | ((u1 >> 6) & 0x1F)); + buf[pos++] = (unsigned char)(0x80 | (u1 & 0x3F)); + } + else + { + buf[pos++] = (unsigned char)(0xE0 | ((u1 >> 12) & 0x0F)); + buf[pos++] = (unsigned char)(0x80 | ((u1 >> 6) & 0x3F)); + buf[pos++] = (unsigned char)(0x80 | (u1 & 0x3F)); + } + } + break; + } + default: free(buf); return 0; + } + } + else + { + buf[pos++] = ch; + } + + if (pos + 8 > cap) + { + cap *= 2; + unsigned char *nb = (unsigned char *)realloc(buf, cap); + if (NULL == nb) + { + free(buf); + return 0; + } + buf = nb; + } + } + *out = buf; + *outLen = pos; + return 1; +} + +static void trim(const char **s, uint32_t *len) +{ + const char *p = *s; + uint32_t n = *len; + while (n && isspace((unsigned char)*p)) + { + p++; + n--; + } + while (n && isspace((unsigned char)p[n - 1])) + { + n--; + } + *s = p; + *len = n; +} + +static int32_t is_quoted_string(const char *s, uint32_t len) +{ + return len >= 2 && s[0] == '\"' && s[len - 1] == '\"'; +} + +int32_t RyanJsonScalarSemanticEqual(const char *a, uint32_t aLen, const char *b, uint32_t bLen) +{ + trim(&a, &aLen); + trim(&b, &bLen); + + if (is_quoted_string(a, aLen) && is_quoted_string(b, bLen)) + { + unsigned char *na = NULL, *nb = NULL; + uint32_t nla = 0, nlb = 0; + if (!RyanJsonNormalizeString(a + 1, aLen - 2, &na, &nla)) { return 0; } + if (!RyanJsonNormalizeString(b + 1, bLen - 2, &nb, &nlb)) + { + free(na); + return 0; + } + int32_t eq = (nla == nlb) && (0 == memcmp(na, nb, nla)); + free(na); + free(nb); + return eq; + } + + if (aLen == 4 && 0 == strncmp(a, "true", 4) && bLen == 4 && 0 == strncmp(b, "true", 4)) { return 1; } + if (aLen == 5 && 0 == strncmp(a, "false", 5) && bLen == 5 && 0 == strncmp(b, "false", 5)) { return 1; } + if (aLen == 4 && 0 == strncmp(a, "null", 4) && bLen == 4 && 0 == strncmp(b, "null", 4)) { return 1; } + + char *endA = NULL, *endB = NULL; + char *bufA = (char *)malloc(aLen + 1); + char *bufB = (char *)malloc(bLen + 1); + if (NULL != bufA && NULL != bufB) + { + memcpy(bufA, a, aLen); + bufA[aLen] = '\0'; + memcpy(bufB, b, bLen); + bufB[bLen] = '\0'; + double va = strtod(bufA, &endA); + double vb = strtod(bufB, &endB); + int32_t okA = (endA && *endA == '\0'); + int32_t okB = (endB && *endB == '\0'); + if (okA && okB) + { + free(bufA); + free(bufB); + + // 使用相对误差比较,处理极大极小值 + if (RyanJsonCompareDouble(va, vb)) { return 1; } + return 0; + + // if (va == vb) { return 1; } + // double absA = fabs(va), absB = fabs(vb); + // double maxAbs = (absA > absB) ? absA : absB; + // if (maxAbs < 1e-300) { return 1; } // 两者都接近零 + // return (fabs(va - vb) / maxAbs) < 1e-14; + } + } + free(bufA); + free(bufB); + return (aLen == bLen) && (0 == memcmp(a, b, aLen)); +} + +int32_t RyanJsonExtractSingleArrayElement(const char *s, uint32_t len, const char **elem, uint32_t *elemLen) +{ + trim(&s, &len); + if (len < 2 || s[0] != '[' || s[len - 1] != ']') { return 0; } + const char *p = s + 1; + uint32_t n = len - 2; + trim(&p, &n); + if (0 == n) { return 0; } + + int32_t in_str = 0, escape = 0; + for (uint32_t i = 0; i < n; i++) + { + char c = p[i]; + if (in_str) + { + if (escape) { escape = 0; } + else if (c == '\\') { escape = 1; } + else if (c == '\"') { in_str = 0; } + } + else + { + if (c == '\"') { in_str = 1; } + else if (c == ',') { return 0; } + } + } + *elem = p; + *elemLen = n; + return 1; +} + +int32_t RyanJsonValueSemanticEqual(const char *a, uint32_t aLen, const char *b, uint32_t bLen) +{ + // 尝试将两个字符串解析为 Json 对象进行语义比较 + // 这可以处理对象和数组的递归比较 + RyanJson_t jsonA = RyanJsonParseOptions(a, aLen, RyanJsonFalse, NULL); + RyanJson_t jsonB = RyanJsonParseOptions(b, bLen, RyanJsonFalse, NULL); + + if (NULL != jsonA && NULL != jsonB) + { + // 使用 RyanJsonCompare 进行完整的语义比较 + int32_t result = RyanJsonCompare(jsonA, jsonB); + (void)RyanJsonDelete(jsonA); + (void)RyanJsonDelete(jsonB); + return result; + } + + // 解析失败时清理并回退到原有逻辑 + if (NULL != jsonA) { (void)RyanJsonDelete(jsonA); } + if (NULL != jsonB) { (void)RyanJsonDelete(jsonB); } + + // 回退:单元素数组提取比较 + const char *ae = NULL, *be = NULL; + uint32_t ale = 0, ble = 0; + if (RyanJsonExtractSingleArrayElement(a, aLen, &ae, &ale) && RyanJsonExtractSingleArrayElement(b, bLen, &be, &ble)) + { + return RyanJsonScalarSemanticEqual(ae, ale, be, ble); + } + return RyanJsonScalarSemanticEqual(a, aLen, b, bLen); +} diff --git a/test/unityTest/cases/RFC8259/testRfc8259Util.h b/test/unityTest/cases/RFC8259/testRfc8259Util.h new file mode 100644 index 0000000..4b1c086 --- /dev/null +++ b/test/unityTest/cases/RFC8259/testRfc8259Util.h @@ -0,0 +1,26 @@ +#ifndef RYAN_JSON_RFC8259_TEST_UTIL_H +#define RYAN_JSON_RFC8259_TEST_UTIL_H + +#include + +/** + * @brief 提取一元素数组的唯一元素;若不是一元素数组返回 0 + */ +int32_t RyanJsonExtractSingleArrayElement(const char *s, uint32_t len, const char **elem, uint32_t *elemLen); + +/** + * @brief 值级语义比较:字符串(去引号并 normalize)、数字(含科学计数法)、布尔、null + */ +int32_t RyanJsonScalarSemanticEqual(const char *a, uint32_t aLen, const char *b, uint32_t bLen); + +/** + * @brief Json 语义比较:支持单元素数组剥离后进行标量比较 + */ +int32_t RyanJsonValueSemanticEqual(const char *a, uint32_t aLen, const char *b, uint32_t bLen); + +/** + * @brief 将 Json 字符串规范化为 UTF-8 字节序列 + */ +int32_t RyanJsonNormalizeString(const char *in, uint32_t inLen, unsigned char **out, uint32_t *outLen); + +#endif // RYAN_JSON_RFC8259_TEST_UTIL_H diff --git a/test/unityTest/cases/core/testChange.c b/test/unityTest/cases/core/testChange.c new file mode 100644 index 0000000..d5f669b --- /dev/null +++ b/test/unityTest/cases/core/testChange.c @@ -0,0 +1,358 @@ +#include "testBase.h" + +static void testChangeEdgeCases(void) +{ + // 测试 NULL 输入处理 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeBoolValue(NULL, true), "ChangeBoolValue(NULL) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeIntValue(NULL, 1), "ChangeIntValue(NULL) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeDoubleValue(NULL, 1.0), "ChangeDoubleValue(NULL) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeStringValue(NULL, "test"), "ChangeStringValue(NULL) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeKey(NULL, "key"), "ChangeKey(NULL) 应返回 False"); + + // 测试类型不匹配处理 + RyanJson_t intNode = RyanJsonCreateInt(NULL, 1); + RyanJson_t doubleNode = RyanJsonCreateDouble(NULL, 1.0); + RyanJson_t boolNode = RyanJsonCreateBool(NULL, RyanJsonTrue); + RyanJson_t strNode = RyanJsonCreateString(NULL, "s"); + RyanJson_t keyedStrNode = RyanJsonCreateString("k", "v"); + RyanJson_t keyedBoolNode = RyanJsonCreateBool("flag", RyanJsonTrue); + TEST_ASSERT_NOT_NULL(intNode); + TEST_ASSERT_NOT_NULL(doubleNode); + TEST_ASSERT_NOT_NULL(boolNode); + TEST_ASSERT_NOT_NULL(strNode); + TEST_ASSERT_NOT_NULL(keyedStrNode); + TEST_ASSERT_NOT_NULL(keyedBoolNode); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeIntValue(doubleNode, 3), "ChangeIntValue(非Int节点) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeDoubleValue(intNode, 3.14), "ChangeDoubleValue(非Double节点) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeBoolValue(intNode, RyanJsonTrue), "ChangeBoolValue(非Bool节点) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeStringValue(intNode, "x"), "ChangeStringValue(非String节点) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeKey(strNode, "k2"), "ChangeKey(无Key的String节点) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeKey(keyedStrNode, NULL), "ChangeKey(key!=NULL 前置条件) 应返回 False"); + + // 失败后原值应保持不变 + TEST_ASSERT_TRUE_MESSAGE(RyanJsonGetBoolValue(boolNode), "bool 初始值错误"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeIntValue(boolNode, 9), "ChangeIntValue(Bool节点) 应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonGetBoolValue(boolNode), "失败后 bool 值不应变化"); + + TEST_ASSERT_EQUAL_INT_MESSAGE(1, RyanJsonGetIntValue(intNode), "int 初始值错误"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeDoubleValue(intNode, 8.8), "ChangeDoubleValue(Int节点) 应返回 False"); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, RyanJsonGetIntValue(intNode), "失败后 int 值不应变化"); + + TEST_ASSERT_EQUAL_STRING_MESSAGE("v", RyanJsonGetStringValue(keyedStrNode), "string 初始值错误"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonChangeBoolValue(keyedStrNode, RyanJsonFalse), "ChangeBoolValue(String节点) 应返回 False"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("v", RyanJsonGetStringValue(keyedStrNode), "失败后 string 值不应变化"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("k", RyanJsonGetKey(keyedStrNode), "失败后 key 不应变化"); + + // 有 key 的非字符串节点允许改 key,且值不应改变 + TEST_ASSERT_TRUE_MESSAGE(RyanJsonChangeKey(keyedBoolNode, "flag2"), "ChangeKey(Bool+Key 节点) 应成功"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("flag2", RyanJsonGetKey(keyedBoolNode), "Bool 节点改 key 失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonGetBoolValue(keyedBoolNode), "Bool 节点改 key 后 value 不应变化"); + + RyanJsonDelete(intNode); + RyanJsonDelete(doubleNode); + RyanJsonDelete(boolNode); + RyanJsonDelete(strNode); + RyanJsonDelete(keyedStrNode); + RyanJsonDelete(keyedBoolNode); +} + +static void testChangeValueStress(void) +{ + RyanJson_t root = RyanJsonCreateObject(); + RyanJsonAddStringToObject(root, "k", "v"); + + // 高频修改 strValue + // 变为超长 + char *longStr = (char *)malloc(1000); + memset(longStr, 'A', 999); + longStr[999] = '\0'; + TEST_ASSERT_TRUE(RyanJsonChangeStringValue(RyanJsonGetObjectByKey(root, "k"), longStr)); + TEST_ASSERT_EQUAL_STRING(longStr, RyanJsonGetStringValue(RyanJsonGetObjectByKey(root, "k"))); + + // 变为特殊字符 + const char *specials = "\t\n\r\b\f\"\\/"; + TEST_ASSERT_TRUE(RyanJsonChangeStringValue(RyanJsonGetObjectByKey(root, "k"), specials)); + TEST_ASSERT_EQUAL_STRING(specials, RyanJsonGetStringValue(RyanJsonGetObjectByKey(root, "k"))); + + // 变为空串 + TEST_ASSERT_TRUE(RyanJsonChangeStringValue(RyanJsonGetObjectByKey(root, "k"), "")); + TEST_ASSERT_EQUAL_STRING("", RyanJsonGetStringValue(RyanJsonGetObjectByKey(root, "k"))); + + // 高频修改 key + // 变为超长 + TEST_ASSERT_TRUE(RyanJsonChangeKey(RyanJsonGetObjectByKey(root, "k"), longStr)); + TEST_ASSERT_TRUE(RyanJsonHasObjectByKey(root, longStr)); + + // 变为空 key + TEST_ASSERT_TRUE(RyanJsonChangeKey(RyanJsonGetObjectByKey(root, longStr), "")); + TEST_ASSERT_TRUE(RyanJsonHasObjectByKey(root, "")); + + free(longStr); + RyanJsonDelete(root); +} + +static void testChangeScalarAndStorageMode(void) +{ + char jsonstr[] = + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," + "\"item\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"]," + "\"array\":[16,16.89,\"hello\",true,false,null]," + "\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]," + "\"string2222\":\"hello\",\"0\":\"1\",\"nameaaaaaaaaaaaaaaaaaaaaaaaaaaaa\":\"Mash\",\"2\":\"3\",\"name\":" + "\"Mashaaaaaaaaaaaaaaaaaaaaaaaa\"}"; + + RyanJson_t jsonRoot = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, "解析基础 Json 失败"); + + /** + * @brief 修改基本类型 + */ + // 修改整数 + RyanJsonChangeIntValue(RyanJsonGetObjectToKey(jsonRoot, "inter"), 20); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(RyanJsonGetObjectToKey(jsonRoot, "inter")), "字段 'inter' 修改后不是整数"); + TEST_ASSERT_EQUAL_INT_MESSAGE(20, RyanJsonGetIntValue(RyanJsonGetObjectToKey(jsonRoot, "inter")), "字段 'inter' 值不匹配"); + + // 修改浮点数 + RyanJsonChangeDoubleValue(RyanJsonGetObjectToKey(jsonRoot, "double"), 20.89); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(RyanJsonGetObjectToKey(jsonRoot, "double")), "字段 'double' 修改后不是浮点数"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareDouble(RyanJsonGetDoubleValue(RyanJsonGetObjectToKey(jsonRoot, "double")), 20.89), + "字段 'double' 值不匹配"); + + // 修改 Key (inline 模式,不超过长度) + RyanJsonChangeKey(RyanJsonGetObjectByKey(jsonRoot, "0"), "type"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("type", RyanJsonGetKey(RyanJsonGetObjectToKey(jsonRoot, "type")), "字段 'type' 的 Key 不匹配"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("1", RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "type")), "字段 'type' 的内容不匹配"); + + // 修改 Key (从 inline 转 ptr 模式) + RyanJsonChangeKey(RyanJsonGetObjectByKey(jsonRoot, "type"), "type000000000000000"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("type000000000000000", RyanJsonGetKey(RyanJsonGetObjectToKey(jsonRoot, "type000000000000000")), + "长 Key 模式下的 Key 不匹配"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("1", RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "type000000000000000")), + "长 Key 模式下的内容不匹配"); + + // 修改 Key (从 ptr 转 inline 模式) + RyanJsonChangeKey(RyanJsonGetObjectByKey(jsonRoot, "nameaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), "na"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("na", RyanJsonGetKey(RyanJsonGetObjectToKey(jsonRoot, "na")), "回退到 inline 模式的 Key 不匹配"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("Mash", RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "na")), + "回退到 inline 模式的内容不匹配"); + + // 修改 Value (inline 模式,不超过长度) + RyanJsonChangeStringValue(RyanJsonGetObjectByKey(jsonRoot, "2"), "type"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("2", RyanJsonGetKey(RyanJsonGetObjectToKey(jsonRoot, "2")), "字段 '2' 的 Key 不匹配"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("type", RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "2")), "字段 '2' 的新内容不匹配"); + + // 修改 Value (从 ptr 转 inline 模式) + RyanJsonChangeStringValue(RyanJsonGetObjectByKey(jsonRoot, "name"), "Ma"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("name", RyanJsonGetKey(RyanJsonGetObjectToKey(jsonRoot, "name")), "字段 'name' 的 Key 不匹配"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("Ma", RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "name")), + "回退到 inline 模式的 Value 不匹配"); + + // 修改 Value (从 ptr 转 ptr 模式) + RyanJsonChangeStringValue(RyanJsonGetObjectByKey(jsonRoot, "name"), "Mashaaaaaaaaaaaaaaaaaaaaaaaa"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("name", RyanJsonGetKey(RyanJsonGetObjectToKey(jsonRoot, "name")), "字段 'name' 的 Key 不匹配"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("Mashaaaaaaaaaaaaaaaaaaaaaaaa", RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "name")), + "长 Value 模式下的内容不匹配"); + + // 修改字符串 + RyanJsonChangeStringValue(RyanJsonGetObjectToKey(jsonRoot, "string"), "world"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(RyanJsonGetObjectToKey(jsonRoot, "string")), "字段 'string' 修改后不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("world", RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "string")), + "字段 'string' 内容不匹配"); + + // 修改 boolValue (true -> false) + RyanJsonChangeBoolValue(RyanJsonGetObjectToKey(jsonRoot, "boolTrue"), RyanJsonFalse); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectToKey(jsonRoot, "boolTrue")), "字段 'boolTrue' 类型错误"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonFalse, RyanJsonGetBoolValue(RyanJsonGetObjectToKey(jsonRoot, "boolTrue")), + "字段 'boolTrue' 值错误"); + + // 修改 boolValue (false -> true) + RyanJsonChangeBoolValue(RyanJsonGetObjectToKey(jsonRoot, "boolFalse"), RyanJsonTrue); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectToKey(jsonRoot, "boolFalse")), "字段 'boolFalse' 类型错误"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonTrue, RyanJsonGetBoolValue(RyanJsonGetObjectToKey(jsonRoot, "boolFalse")), + "字段 'boolFalse' 值错误"); + + /** + * @brief 修改数组元素 (arrayInt) + */ + RyanJsonChangeIntValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(jsonRoot, "arrayInt"), 0), 99); + TEST_ASSERT_EQUAL_INT_MESSAGE(99, RyanJsonGetIntValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(jsonRoot, "arrayInt"), 0)), + "数组元素修改失败"); + + /** + * @brief 修改数组元素 (arrayDouble) + */ + RyanJsonChangeDoubleValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(jsonRoot, "arrayDouble"), 1), 99.99); + TEST_ASSERT_TRUE_MESSAGE( + RyanJsonCompareDouble(RyanJsonGetDoubleValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(jsonRoot, "arrayDouble"), 1)), + 99.99), + "数组浮点元素修改失败"); + + /** + * @brief 修改数组元素 (arrayString) + */ + RyanJsonChangeStringValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(jsonRoot, "arrayString"), 2), "changedString"); + TEST_ASSERT_EQUAL_STRING_MESSAGE( + "changedString", RyanJsonGetStringValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(jsonRoot, "arrayString"), 2)), + "数组字符串元素修改失败"); + + /** + * @brief 修改嵌套对象 + */ + RyanJson_t nestedObj = RyanJsonGetObjectToKey(jsonRoot, "item"); + RyanJsonChangeStringValue(RyanJsonGetObjectToKey(nestedObj, "string"), "nestedWorld"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("nestedWorld", RyanJsonGetStringValue(RyanJsonGetObjectToKey(nestedObj, "string")), + "嵌套对象修改失败"); + + /** + * @brief 修改数组对象中的字段 (arrayItem[0].inter -> 123) + */ + RyanJson_t arrayItem0 = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(jsonRoot, "arrayItem"), 0); + RyanJsonChangeIntValue(RyanJsonGetObjectToKey(arrayItem0, "inter"), 123); + TEST_ASSERT_EQUAL_INT_MESSAGE(123, RyanJsonGetIntValue(RyanJsonGetObjectToKey(arrayItem0, "inter")), "数组中对象的字段修改失败"); + + char *str = RyanJsonPrint(jsonRoot, 1024, RyanJsonTrue, NULL); + RyanJsonFree(str); + RyanJsonDelete(jsonRoot); + + /** + * @brief 验证创建后的内存模式库切换 (Create -> Change) + */ + RyanJson_t json = RyanJsonCreateString("key", "val"); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_EQUAL_STRING_MESSAGE("val", RyanJsonGetStringValue(json), "Inline 模式初次设置失败"); + + // 修改为长字符串 (触发自动切换到 ptr 模式) + const char *longVal = "This is a very long string that definitely exceeds the inline threshold of RyanJson structure."; + RyanJsonChangeStringValue(json, longVal); + TEST_ASSERT_EQUAL_STRING_MESSAGE(longVal, RyanJsonGetStringValue(json), "切换到 Ptr 模式后值错误"); + + // 修改回短字符串 + RyanJsonChangeStringValue(json, "new"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("new", RyanJsonGetStringValue(json), "切回短字符串后值错误"); + + // 修改 Key 为长 Key + const char *longKey = "a_very_long_key_name_to_trigger_ptr_mode_for_key_storage_in_ryanjson"; + RyanJsonChangeKey(json, longKey); + TEST_ASSERT_EQUAL_STRING_MESSAGE(longKey, RyanJsonGetKey(json), "Key 切换到 Ptr 模式后错误"); + + RyanJsonDelete(json); +} + +static void testChangeInlineCalcBoundary(void) +{ + /** + * 这个测试的目标: + * 验证 RyanJsonInternalChangeString 的容量判断在“临界长度”处是否正确; + * 验证 value 和 key 两条路径都会发生 inline <-> ptr 的双向切换; + * 不依赖固定常量(例如 keyLen=255),而是基于当前编译配置动态计算边界。 + * + * 背景: + * RyanJsonInternalChangeString 的判定核心是: + * if ((mallocSize + keyLenFieldBytes) <= RyanJsonInlineStringSize) => inline + * else => ptr + * + * 因此这里分别构造: + * - 恰好满足 <= 的输入(应保持/切回 inline) + * - 超过 1 字节的输入(应切到 ptr) + */ + uint32_t inlineSize = RyanJsonInlineStringSize; + uint32_t fixedKeyLen = 1; + uint32_t fixedValueLen = 1; + + /** + * value 边界计算(固定 key="k"): + * 需要占用的总 inline 空间 = keyFieldLen + keyLen + '\0' + valueLen + '\0' + * 先算出不含 value 内容本体时的固定开销 baseNeedForValue, + * 再反推出 value 能放进 inline 的最大长度 maxInlineValueLen。 + */ + uint32_t keyFieldLenForValue = RyanJsonInternalDecodeKeyLenField(RyanJsonInternalCalcLenBytes(fixedKeyLen)); + uint32_t baseNeedForValue = keyFieldLenForValue + fixedKeyLen + 1 + 1; // keyField + key + '\0' + value '\0' + TEST_ASSERT_TRUE_MESSAGE(inlineSize >= baseNeedForValue, "inline 大小异常,无法完成边界测试"); + + uint32_t maxInlineValueLen = inlineSize - baseNeedForValue; + // valueInline: 恰好命中 inline 临界;valuePtr: 比临界多 1 字节,必须走 ptr + char *valueInline = (char *)malloc((size_t)maxInlineValueLen + 1U); + char *valuePtr = (char *)malloc((size_t)maxInlineValueLen + 2U); + TEST_ASSERT_NOT_NULL(valueInline); + TEST_ASSERT_NOT_NULL(valuePtr); + memset(valueInline, 'v', maxInlineValueLen); + valueInline[maxInlineValueLen] = '\0'; + memset(valuePtr, 'p', maxInlineValueLen + 1U); + valuePtr[maxInlineValueLen + 1U] = '\0'; + + RyanJson_t valueNode = RyanJsonCreateString("k", ""); + TEST_ASSERT_NOT_NULL(valueNode); + // 临界长度:应为 inline + TEST_ASSERT_TRUE(RyanJsonChangeStringValue(valueNode, valueInline)); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonGetPayloadStrIsPtrByFlag(valueNode), "value 临界长度应为 inline"); + // 超临界:应切到 ptr + TEST_ASSERT_TRUE(RyanJsonChangeStringValue(valueNode, valuePtr)); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonGetPayloadStrIsPtrByFlag(valueNode), "value 超过临界长度应切到 ptr"); + // 回退到临界:应能从 ptr 回到 inline(覆盖回切逻辑) + TEST_ASSERT_TRUE(RyanJsonChangeStringValue(valueNode, valueInline)); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonGetPayloadStrIsPtrByFlag(valueNode), "value 回退到临界长度应切回 inline"); + + /** + * key 边界计算(固定 value="v"): + * keyLen 变化时,keyLenField 可能从 1/2/4 字节变化, + * 所以不能硬编码某个 keyLen,必须遍历找到“当前配置下最大可 inline 的 key 长度”。 + */ + uint32_t maxInlineKeyLen = 0; + for (uint32_t keyLen = 0; keyLen <= inlineSize; keyLen++) + { + uint32_t keyFieldLen = RyanJsonInternalDecodeKeyLenField(RyanJsonInternalCalcLenBytes(keyLen)); + uint32_t need = keyFieldLen + keyLen + 1 + fixedValueLen + 1; + if (need <= inlineSize) { maxInlineKeyLen = keyLen; } + } + + // 交叉校验:maxInlineKeyLen 应满足 inline;maxInlineKeyLen+1 应超过 inline + uint32_t inlineNeed = + RyanJsonInternalDecodeKeyLenField(RyanJsonInternalCalcLenBytes(maxInlineKeyLen)) + maxInlineKeyLen + 1 + fixedValueLen + 1; + uint32_t ptrNeed = RyanJsonInternalDecodeKeyLenField(RyanJsonInternalCalcLenBytes(maxInlineKeyLen + 1U)) + (maxInlineKeyLen + 1U) + + 1 + fixedValueLen + 1; + TEST_ASSERT_TRUE_MESSAGE(inlineNeed <= inlineSize, "inline key 临界值计算错误"); + TEST_ASSERT_TRUE_MESSAGE(ptrNeed > inlineSize, "ptr key 临界值计算错误"); + + // keyInline: 恰好临界;keyPtr: 超临界 1 字节 + char *keyInline = (char *)malloc((size_t)maxInlineKeyLen + 1U); + char *keyPtr = (char *)malloc((size_t)maxInlineKeyLen + 2U); + TEST_ASSERT_NOT_NULL(keyInline); + TEST_ASSERT_NOT_NULL(keyPtr); + memset(keyInline, 'k', maxInlineKeyLen); + keyInline[maxInlineKeyLen] = '\0'; + memset(keyPtr, 'K', maxInlineKeyLen + 1U); + keyPtr[maxInlineKeyLen + 1U] = '\0'; + + RyanJson_t keyNode = RyanJsonCreateString("", "v"); + TEST_ASSERT_NOT_NULL(keyNode); + // key 临界长度:应为 inline + TEST_ASSERT_TRUE(RyanJsonChangeKey(keyNode, keyInline)); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonGetPayloadStrIsPtrByFlag(keyNode), "key 临界长度应为 inline"); + // key 超临界:应切到 ptr + TEST_ASSERT_TRUE(RyanJsonChangeKey(keyNode, keyPtr)); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonGetPayloadStrIsPtrByFlag(keyNode), "key 超过临界长度应切到 ptr"); + // key 回退临界:应切回 inline + TEST_ASSERT_TRUE(RyanJsonChangeKey(keyNode, keyInline)); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonGetPayloadStrIsPtrByFlag(keyNode), "key 回退到临界长度应切回 inline"); + + // 释放临时资源,避免单测内存泄漏 + RyanJsonDelete(valueNode); + RyanJsonDelete(keyNode); + free(valueInline); + free(valuePtr); + free(keyInline); + free(keyPtr); +} + +void testChangeRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testChangeEdgeCases); + RUN_TEST(testChangeValueStress); + RUN_TEST(testChangeScalarAndStorageMode); + RUN_TEST(testChangeInlineCalcBoundary); +} diff --git a/test/unityTest/cases/core/testCompare.c b/test/unityTest/cases/core/testCompare.c new file mode 100644 index 0000000..6a9e0c5 --- /dev/null +++ b/test/unityTest/cases/core/testCompare.c @@ -0,0 +1,593 @@ +#include "testBase.h" +static void testCompareEdgeCases(void) +{ + // 测试对象键值对顺序对比较的影响 + // RyanJson 的对象比较是无序的:键值对顺序不同也应视为相同 + + RyanJson_t json1 = RyanJsonCreateObject(); + RyanJsonAddIntToObject(json1, "a", 1); + RyanJsonAddIntToObject(json1, "b", 2); + + RyanJson_t json2 = RyanJsonCreateObject(); + RyanJsonAddIntToObject(json2, "b", 2); + RyanJsonAddIntToObject(json2, "a", 1); // 顺序不同 + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(json1, json2), "顺序不同的对象比较应返回 True (RyanJson 是无序比较)"); + + RyanJsonDelete(json1); + RyanJsonDelete(json2); +} + +static void testCompareObjectOrderPaths(void) +{ + RyanJson_t left = RyanJsonCreateObject(); + RyanJson_t rightOrdered = RyanJsonCreateObject(); + RyanJson_t rightUnordered = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(left); + TEST_ASSERT_NOT_NULL(rightOrdered); + TEST_ASSERT_NOT_NULL(rightUnordered); + + // 同序构造:覆盖对象比较同序快路径 + for (int32_t i = 0; i < 64; i++) + { + char key[16]; + RyanJsonSnprintf(key, sizeof(key), "k%" PRId32, i); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(left, key, i)); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(rightOrdered, key, i)); + } + + // 逆序构造:覆盖对象比较按 key 回退查找路径 + for (int32_t i = 63; i >= 0; i--) + { + char key[16]; + RyanJsonSnprintf(key, sizeof(key), "k%" PRId32, i); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(rightUnordered, key, i)); + } + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(left, rightOrdered), "同序对象比较应返回 True"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(left, rightUnordered), "乱序对象比较应返回 True"); + + RyanJsonDelete(left); + RyanJsonDelete(rightOrdered); + RyanJsonDelete(rightUnordered); +} + +static void testCompareScalarAndTypeMatrix(void) +{ + RyanJson_t intLeft = RyanJsonCreateInt(NULL, 10); + RyanJson_t intRight = RyanJsonCreateInt(NULL, 11); + RyanJson_t doubleCloseLeft = RyanJsonCreateDouble(NULL, 1.0); + RyanJson_t doubleCloseRight = RyanJsonCreateDouble(NULL, 1.0 + (RyanJsonAbsTolerance / 2.0)); + RyanJson_t doubleFarLeft = RyanJsonCreateDouble(NULL, 1.0); + RyanJson_t doubleFarRight = RyanJsonCreateDouble(NULL, 1.0 + (RyanJsonAbsTolerance * 100.0)); + RyanJson_t strLeft = RyanJsonCreateString(NULL, "alpha"); + RyanJson_t strRight = RyanJsonCreateString(NULL, "beta"); + RyanJson_t typeInt = RyanJsonCreateInt(NULL, 1); + RyanJson_t typeDouble = RyanJsonCreateDouble(NULL, 1.0); + RyanJson_t typeString = RyanJsonCreateString(NULL, "1"); + + TEST_ASSERT_NOT_NULL(intLeft); + TEST_ASSERT_NOT_NULL(intRight); + TEST_ASSERT_NOT_NULL(doubleCloseLeft); + TEST_ASSERT_NOT_NULL(doubleCloseRight); + TEST_ASSERT_NOT_NULL(doubleFarLeft); + TEST_ASSERT_NOT_NULL(doubleFarRight); + TEST_ASSERT_NOT_NULL(strLeft); + TEST_ASSERT_NOT_NULL(strRight); + TEST_ASSERT_NOT_NULL(typeInt); + TEST_ASSERT_NOT_NULL(typeDouble); + TEST_ASSERT_NOT_NULL(typeString); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(intLeft, intRight), "int 值不同时 Compare 应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(intLeft, intRight), "int 值不同时 CompareOnlyKey 应返回 True"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(doubleCloseLeft, doubleCloseRight), "double 在容差内应判等"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(doubleFarLeft, doubleFarRight), "double 超过容差应不相等"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(doubleFarLeft, doubleFarRight), "double 值不同时 CompareOnlyKey 应返回 True"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(strLeft, strRight), "string 值不同时 Compare 应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(strLeft, strRight), "string 值不同时 CompareOnlyKey 应返回 True"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(typeInt, typeString), "类型不同时 Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(typeInt, typeString), "类型不同时 CompareOnlyKey 应返回 False"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(typeInt, typeDouble), "int/double 即使数值相同,类型不同也应不相等"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(typeInt, typeDouble), + "CompareOnlyKey 忽略 number 具体子类型,int/double 应视为相等"); + + RyanJsonDelete(intLeft); + RyanJsonDelete(intRight); + RyanJsonDelete(doubleCloseLeft); + RyanJsonDelete(doubleCloseRight); + RyanJsonDelete(doubleFarLeft); + RyanJsonDelete(doubleFarRight); + RyanJsonDelete(strLeft); + RyanJsonDelete(strRight); + RyanJsonDelete(typeInt); + RyanJsonDelete(typeDouble); + RyanJsonDelete(typeString); +} + +static void testCompareNumberSubtypeInContainers(void) +{ + RyanJson_t left = RyanJsonParse("{\"n\":1,\"arr\":[1,2.0],\"obj\":{\"x\":3,\"y\":4.0}}"); + RyanJson_t right = RyanJsonParse("{\"obj\":{\"y\":4,\"x\":3.0},\"arr\":[1.0,2],\"n\":1.0}"); + RyanJson_t rightTypeMismatch = RyanJsonParse("{\"obj\":{\"y\":4,\"x\":\"3\"},\"arr\":[1.0,2],\"n\":1.0}"); + + TEST_ASSERT_NOT_NULL(left); + TEST_ASSERT_NOT_NULL(right); + TEST_ASSERT_NOT_NULL(rightTypeMismatch); + + // 全量比较要求数值子类型一致;仅比较 key 时允许 int/double 混用 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(left, right), "容器中 int/double 子类型不同,Compare 应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(left, right), + "容器中 int/double 子类型不同,但结构一致时 CompareOnlyKey 应返回 True"); + + // 数值与字符串类型不同,即使在 CompareOnlyKey 下也必须失败 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(left, rightTypeMismatch), "number/string 类型不同,Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(left, rightTypeMismatch), "number/string 类型不同,CompareOnlyKey 也应返回 False"); + + RyanJsonDelete(left); + RyanJsonDelete(right); + RyanJsonDelete(rightTypeMismatch); +} + +static void testCompareEmptyContainerAndTypeMismatch(void) +{ + RyanJson_t emptyObjA = RyanJsonCreateObject(); + RyanJson_t emptyObjB = RyanJsonCreateObject(); + RyanJson_t emptyArrA = RyanJsonCreateArray(); + RyanJson_t emptyArrB = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(emptyObjA); + TEST_ASSERT_NOT_NULL(emptyObjB); + TEST_ASSERT_NOT_NULL(emptyArrA); + TEST_ASSERT_NOT_NULL(emptyArrB); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(emptyObjA, emptyObjB), "空对象之间应相等"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(emptyObjA, emptyObjB), "空对象 CompareOnlyKey 应相等"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(emptyArrA, emptyArrB), "空数组之间应相等"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(emptyArrA, emptyArrB), "空数组 CompareOnlyKey 应相等"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(emptyObjA, emptyArrA), "对象与数组类型不同应不相等"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(emptyObjA, emptyArrA), "对象与数组 CompareOnlyKey 也应不相等"); + + RyanJsonDelete(emptyObjA); + RyanJsonDelete(emptyObjB); + RyanJsonDelete(emptyArrA); + RyanJsonDelete(emptyArrB); +} + +static void testCompareArraySemantics(void) +{ + RyanJson_t arrIntA = RyanJsonCreateArray(); + RyanJson_t arrIntB = RyanJsonCreateArray(); + RyanJson_t arrIntReverse = RyanJsonCreateArray(); + RyanJson_t arrMixedA = RyanJsonCreateArray(); + RyanJson_t arrMixedB = RyanJsonCreateArray(); + RyanJson_t arrShort = RyanJsonCreateArray(); + + TEST_ASSERT_NOT_NULL(arrIntA); + TEST_ASSERT_NOT_NULL(arrIntB); + TEST_ASSERT_NOT_NULL(arrIntReverse); + TEST_ASSERT_NOT_NULL(arrMixedA); + TEST_ASSERT_NOT_NULL(arrMixedB); + TEST_ASSERT_NOT_NULL(arrShort); + + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntA, 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntA, 2)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntA, 3)); + + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntB, 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntB, 2)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntB, 3)); + + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntReverse, 3)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntReverse, 2)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrIntReverse, 1)); + + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrMixedA, 1)); + TEST_ASSERT_TRUE(RyanJsonAddStringToArray(arrMixedA, "2")); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrMixedA, 3)); + + TEST_ASSERT_TRUE(RyanJsonAddStringToArray(arrMixedB, "1")); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrMixedB, 2)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrMixedB, 3)); + + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrShort, 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arrShort, 2)); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(arrIntA, arrIntB), "相同数组 Compare 应返回 True"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(arrIntA, arrIntReverse), "数组顺序不同 Compare 应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(arrIntA, arrIntReverse), "数组顺序不同但类型一致,CompareOnlyKey 应返回 True"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(arrMixedA, arrMixedB), "数组元素类型顺序不同 Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(arrMixedA, arrMixedB), "数组元素类型顺序不同 CompareOnlyKey 应返回 False"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(arrIntA, arrShort), "数组长度不同 Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(arrIntA, arrShort), "数组长度不同 CompareOnlyKey 应返回 False"); + + RyanJsonDelete(arrIntA); + RyanJsonDelete(arrIntB); + RyanJsonDelete(arrIntReverse); + RyanJsonDelete(arrMixedA); + RyanJsonDelete(arrMixedB); + RyanJsonDelete(arrShort); +} + +static void testCompareNestedObjectScenarios(void) +{ + RyanJson_t left = RyanJsonParse("{\"meta\":{\"id\":1,\"name\":\"m\"},\"list\":[1,2,3],\"flag\":true}"); + RyanJson_t rightOrderedDiff = RyanJsonParse("{\"flag\":true,\"list\":[1,2,3],\"meta\":{\"name\":\"m\",\"id\":1}}"); + RyanJson_t rightValueDiff = RyanJsonParse("{\"flag\":true,\"list\":[1,2,3],\"meta\":{\"name\":\"m\",\"id\":2}}"); + RyanJson_t rightMissingKey = RyanJsonParse("{\"flag\":true,\"list\":[1,2,3],\"meta\":{\"name\":\"m\",\"idx\":1}}"); + RyanJson_t leftSingleKey = RyanJsonParse("{\"a\":1}"); + RyanJson_t rightSingleKey = RyanJsonParse("{\"b\":1}"); + RyanJson_t leftPrefixMatch = RyanJsonParse("{\"a\":1,\"b\":2}"); + RyanJson_t rightPrefixMismatch = RyanJsonParse("{\"a\":1,\"c\":2}"); + RyanJson_t leftTailMatch = RyanJsonParse("{\"a\":1,\"b\":2,\"c\":3}"); + RyanJson_t rightTailMatch = RyanJsonParse("{\"b\":2,\"c\":3,\"a\":1}"); + RyanJson_t rightTailMismatch = RyanJsonParse("{\"c\":3,\"d\":4,\"a\":1}"); + + TEST_ASSERT_NOT_NULL(left); + TEST_ASSERT_NOT_NULL(rightOrderedDiff); + TEST_ASSERT_NOT_NULL(rightValueDiff); + TEST_ASSERT_NOT_NULL(rightMissingKey); + TEST_ASSERT_NOT_NULL(leftSingleKey); + TEST_ASSERT_NOT_NULL(rightSingleKey); + TEST_ASSERT_NOT_NULL(leftPrefixMatch); + TEST_ASSERT_NOT_NULL(rightPrefixMismatch); + TEST_ASSERT_NOT_NULL(leftTailMatch); + TEST_ASSERT_NOT_NULL(rightTailMatch); + TEST_ASSERT_NOT_NULL(rightTailMismatch); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(left, rightOrderedDiff), "嵌套对象乱序但值一致应返回 True"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(left, rightOrderedDiff), "嵌套对象乱序 CompareOnlyKey 应返回 True"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(left, rightValueDiff), "嵌套对象值不同 Compare 应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(left, rightValueDiff), "嵌套对象值不同 CompareOnlyKey 应返回 True"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(left, rightMissingKey), "嵌套对象 key 不匹配 Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(left, rightMissingKey), "嵌套对象 key 不匹配 CompareOnlyKey 应返回 False"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(leftSingleKey, rightSingleKey), "同尺寸对象但 key 不同 Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(leftSingleKey, rightSingleKey), + "同尺寸对象但 key 不同 CompareOnlyKey 应返回 False"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(leftPrefixMatch, rightPrefixMismatch), + "前缀 key 相同但后续 key 不同 Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(leftPrefixMatch, rightPrefixMismatch), + "前缀 key 相同但后续 key 不同 CompareOnlyKey 应返回 False"); + + // 覆盖同层遍历中 rightCandidate == NULL 的回退查找成功分支 + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(leftTailMatch, rightTailMatch), "尾节点命中回退查找后 Compare 应返回 True"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(leftTailMatch, rightTailMatch), "尾节点命中回退查找后 CompareOnlyKey 应返回 True"); + + // 覆盖同层遍历中 rightCandidate == NULL 的回退查找失败分支 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(leftTailMatch, rightTailMismatch), "尾节点回退查找失败 Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(leftTailMatch, rightTailMismatch), + "尾节点回退查找失败 CompareOnlyKey 应返回 False"); + + RyanJsonDelete(left); + RyanJsonDelete(rightOrderedDiff); + RyanJsonDelete(rightValueDiff); + RyanJsonDelete(rightMissingKey); + RyanJsonDelete(leftSingleKey); + RyanJsonDelete(rightSingleKey); + RyanJsonDelete(leftPrefixMatch); + RyanJsonDelete(rightPrefixMismatch); + RyanJsonDelete(leftTailMatch); + RyanJsonDelete(rightTailMatch); + RyanJsonDelete(rightTailMismatch); +} + +static void testCompareArrayWithObjects(void) +{ + RyanJson_t arrLeft = RyanJsonParse("[{\"a\":1,\"b\":2},{\"x\":3,\"y\":4}]"); + RyanJson_t arrObjectUnordered = RyanJsonParse("[{\"b\":2,\"a\":1},{\"y\":4,\"x\":3}]"); + RyanJson_t arrOrderSwapped = RyanJsonParse("[{\"y\":4,\"x\":3},{\"b\":2,\"a\":1}]"); + RyanJson_t arrValueDiff = RyanJsonParse("[{\"a\":9,\"b\":2},{\"x\":3,\"y\":4}]"); + + TEST_ASSERT_NOT_NULL(arrLeft); + TEST_ASSERT_NOT_NULL(arrObjectUnordered); + TEST_ASSERT_NOT_NULL(arrOrderSwapped); + TEST_ASSERT_NOT_NULL(arrValueDiff); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(arrLeft, arrObjectUnordered), "数组内对象乱序键应比较为 True"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(arrLeft, arrObjectUnordered), "数组内对象乱序键 CompareOnlyKey 应返回 True"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(arrLeft, arrOrderSwapped), "数组元素顺序变化 Compare 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(arrLeft, arrOrderSwapped), "数组元素顺序变化 CompareOnlyKey 应返回 False"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(arrLeft, arrValueDiff), "数组内对象值变化 Compare 应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(arrLeft, arrValueDiff), "数组内对象值变化但键结构一致 CompareOnlyKey 应返回 True"); + + RyanJsonDelete(arrLeft); + RyanJsonDelete(arrObjectUnordered); + RyanJsonDelete(arrOrderSwapped); + RyanJsonDelete(arrValueDiff); +} + +static void testCompareDeepNestAndLargeArray(void) +{ + // 深度嵌套比较(检测栈溢出或递归限制) + // 之前 200 层出现疑似内存不足或其他问题,降至 50 层验证核心逻辑 + int32_t depth = 50; + RyanJson_t root1 = RyanJsonCreateObject(); + RyanJson_t root2 = RyanJsonCreateObject(); + + RyanJson_t curr1 = root1; + RyanJson_t curr2 = root2; + + for (int32_t i = 0; i < depth; i++) + { + RyanJson_t child1 = RyanJsonCreateObject(); + RyanJson_t child2 = RyanJsonCreateObject(); + + TEST_ASSERT_NOT_NULL_MESSAGE(child1, "创建 child1 失败"); + TEST_ASSERT_NOT_NULL_MESSAGE(child2, "创建 child2 失败"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddItemToObject(curr1, "nest", child1), "添加到 curr1 失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddItemToObject(curr2, "nest", child2), "添加到 curr2 失败"); + + // RyanJsonAddItemToObject 会“吞掉” child1(若其为容器),把其内容转移到新节点并释放 child1 + // 因此 child1 指针已失效,必须通过 key 获取新创建的节点 + curr1 = RyanJsonGetObjectByKey(curr1, "nest"); + curr2 = RyanJsonGetObjectByKey(curr2, "nest"); + + TEST_ASSERT_NOT_NULL_MESSAGE(curr1, "获取 curr1 下一级 nest 失败"); + TEST_ASSERT_NOT_NULL_MESSAGE(curr2, "获取 curr2 下一级 nest 失败"); + } + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(root1, root2), "初始深度比较失败"); + + // 修改末端 + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddIntToObject(curr1, "diff", 1), "添加 diff 失败"); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, RyanJsonGetSize(curr1), "添加 diff 后 Size 应为 1"); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, RyanJsonGetSize(curr2), "curr2 Size 应仍为 0"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(root1, root2), "修改后深度比较应失败"); + + RyanJsonDelete(root1); + RyanJsonDelete(root2); + + // 大数组比较 + int32_t count = 2000; + RyanJson_t arr1 = RyanJsonCreateArray(); + RyanJson_t arr2 = RyanJsonCreateArray(); + + for (int32_t i = 0; i < count; i++) + { + RyanJsonAddIntToArray(arr1, i); + RyanJsonAddIntToArray(arr2, i); + } + TEST_ASSERT_TRUE(RyanJsonCompare(arr1, arr2)); + + // 修改中间一个元素 + RyanJsonDeleteByIndex(arr2, count / 2); // 删除中间项 + RyanJson_t insert = RyanJsonCreateInt("new", 99999); + RyanJsonInsert(arr2, count / 2, insert); + + TEST_ASSERT_FALSE(RyanJsonCompare(arr1, arr2)); + + RyanJsonDelete(arr1); + RyanJsonDelete(arr2); +} + +static void testCompareEqualityAndStructuralDiff(void) +{ + char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" + "{\"inter\":16,\"double\":16." + "89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," + "16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," + "\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null}]}"; + + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Json 1 失败"); + RyanJson_t json2 = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json2, "解析 Json 2 失败"); + + // 边界情况测试 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, NULL), "与 NULL 比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(NULL, json2), "NULL 与对象比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(NULL, NULL), "NULL 与 NULL 比较应返回 False"); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, NULL), "仅比较 Key:与 NULL 比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(NULL, json2), "仅比较 Key:NULL 与对象比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(NULL, NULL), "仅比较 Key:NULL 与 NULL 比较应返回 False"); + + // 完整对象比较 + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(json, json2), "两个相同内容的对象比较应返回 True"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(json, json), "对象与自身比较应返回 True"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "仅比较 Key:两个相同内容的对象比较应返回 True"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json), "仅比较 Key:对象与自身比较应返回 True"); + + // 修改对象 2 并比较 + // 添加字符串 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddStringToObject(json2, "test", "hello"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "多出一个字段后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "多出一个字段后仅比较 Key 应返回 False"); + + // 添加整数 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddIntToObject(json2, "test", 1); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "多出一个整数后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "多出一个整数后仅比较 Key 应返回 False"); + + // 添加浮点数 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddDoubleToObject(json2, "test", 2.0); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "多出一个浮点数后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "多出一个浮点数后仅比较 Key 应返回 False"); + + // 添加 boolValue + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddBoolToObject(json2, "test", RyanJsonTrue); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "多出一个布尔值后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "多出一个布尔值后仅比较 Key 应返回 False"); + + // 添加 null + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddNullToObject(json2, "test"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "多出一个 Null 后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "多出一个 Null 后仅比较 Key 应返回 False"); + + // 数组修改测试 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddIntToArray(RyanJsonGetObjectToKey(json2, "arrayInt"), 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "数组长度变化后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "数组长度变化后仅比较 Key 应返回 False"); + + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddDoubleToArray(RyanJsonGetObjectToKey(json2, "arrayDouble"), 2.0); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "数组长度变化(浮点)后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "数组长度变化(浮点)后仅比较 Key 应返回 False"); + + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddStringToArray(RyanJsonGetObjectToKey(json2, "arrayString"), "hello"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "数组长度变化(字符串)后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "数组长度变化(字符串)后仅比较 Key 应返回 False"); + + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonAddStringToArray(RyanJsonGetObjectToKey(json2, "arrayItem"), "hello"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "数组长度变化(项)后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "数组长度变化(项)后仅比较 Key 应返回 False"); + + // 修改 key 名称 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeKey(RyanJsonGetObjectToKey(json2, "inter"), "int2"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "Key 修改后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "Key 修改后仅比较 Key 应返回 False"); + + // 修改值但 key 相同 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeIntValue(RyanJsonGetObjectToKey(json2, "inter"), 17); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "Value 修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "Value 修改但 Key 相同,仅比较 Key 应返回 True"); + + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeDoubleValue(RyanJsonGetObjectToKey(json2, "double"), 20.89); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "浮点 Value 修改但 Key 相同,仅比较 Key 应返回 True"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "浮点 Value 修改后比较应返回 False"); + + // 类型修改测试(从 double 改为 int32_t) + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonDeleteByKey(json2, "double"); + RyanJsonAddIntToObject(json2, "double", 20); // 改为 int + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "类型修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "类型修改但 Key 相同,仅比较 Key 应返回 True"); + + // 修改 strValue + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeStringValue(RyanJsonGetObjectToKey(json2, "string"), "49"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "字符串 Value 修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "字符串 Value 修改但 Key 相同,仅比较 Key 应返回 True"); + + // 修改对象 1 的 boolValue + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeBoolValue(RyanJsonGetObjectToKey(json2, "boolTrue"), RyanJsonFalse); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "布尔 Value 修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "布尔 Value 修改但 Key 相同,仅比较 Key 应返回 True"); + + // 修改嵌套对象的 boolValue + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeBoolValue(RyanJsonGetObjectToKey(json2, "item", "boolTrue"), RyanJsonFalse); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "嵌套布尔 Value 修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "嵌套布尔 Value 修改但结构相同,仅比较 Key 应返回 True"); + + // 修改数组中的整数 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeIntValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "arrayInt"), 0), 17); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "数组元素修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "数组元素修改但长度相同,仅比较 Key 应返回 True"); + + // 修改数组中的浮点数 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeDoubleValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "arrayDouble"), 0), 20.89); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "数组浮点元素修改后比较应返回 False"); + + // 修改数组中的字符串 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeStringValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "arrayString"), 0), "20.89"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "数组字符串元素修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "数组字符串元素修改但长度相同,仅比较 Key 应返回 True"); + + // 修改混合数组 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeIntValue(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "array"), 0), 17); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "混合数组修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "混合数组修改但长度相同,仅比较 Key 应返回 True"); + + // 修改数组项中的对象 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonChangeIntValue(RyanJsonGetObjectToKey(RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json2, "arrayItem"), 0), "inter"), + 17); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "数组项对象修改后比较应返回 False"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "数组项对象修改但结构相同,仅比较 Key 应返回 True"); + + // 删除整个 key 节点 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonDeleteByKey(json2, "arrayItem"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "删除 Key 后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "删除 Key 后仅比较 Key 应返回 False"); + + // 删除数组索引项 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonDeleteByIndex(RyanJsonGetObjectToKey(json2, "arrayInt"), 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "删除数组索引后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "删除数组索引后仅比较 Key 应返回 False"); + + // 删除数组项中的对象项 + RyanJsonDelete(json2); + json2 = RyanJsonParse(jsonstr); + RyanJsonDeleteByIndex(RyanJsonGetObjectToKey(json2, "arrayItem"), 0); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompare(json, json2), "删除数组对象项后比较应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonCompareOnlyKey(json, json2), "删除数组对象项后仅比较 Key 应返回 False"); + + RyanJsonDelete(json); + RyanJsonDelete(json2); +} + +void testCompareRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testCompareEdgeCases); + RUN_TEST(testCompareObjectOrderPaths); + RUN_TEST(testCompareScalarAndTypeMatrix); + RUN_TEST(testCompareNumberSubtypeInContainers); + RUN_TEST(testCompareEmptyContainerAndTypeMismatch); + RUN_TEST(testCompareArraySemantics); + RUN_TEST(testCompareNestedObjectScenarios); + RUN_TEST(testCompareArrayWithObjects); + RUN_TEST(testCompareDeepNestAndLargeArray); + RUN_TEST(testCompareEqualityAndStructuralDiff); +} diff --git a/test/unityTest/cases/core/testCreate.c b/test/unityTest/cases/core/testCreate.c new file mode 100644 index 0000000..780fe83 --- /dev/null +++ b/test/unityTest/cases/core/testCreate.c @@ -0,0 +1,445 @@ +#include "testBase.h" + +static int32_t gCreateFailAfter = -1; +static int32_t gCreateAllocCount = 0; + +static void *createFailMalloc(size_t size) +{ + if (gCreateFailAfter >= 0 && gCreateAllocCount++ >= gCreateFailAfter) { return NULL; } + return unityTestMalloc(size); +} + +static void *createFailRealloc(void *block, size_t size) +{ + if (gCreateFailAfter >= 0 && gCreateAllocCount++ >= gCreateFailAfter) { return NULL; } + return unityTestRealloc(block, size); +} + +static void createSetFailAfter(int32_t n) +{ + gCreateFailAfter = n; + gCreateAllocCount = 0; + RyanJsonInitHooks(createFailMalloc, unityTestFree, createFailRealloc); +} + +static void createRestoreHooks(void) +{ + RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc); + gCreateFailAfter = -1; + gCreateAllocCount = 0; +} + +static void testCreateEdgeCases(void) +{ + // 测试创建空列表 + RyanJson_t emptyArr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(emptyArr); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetArraySize(emptyArr)); + RyanJsonDelete(emptyArr); + + // 测试通过辅助函数创建空列表 + // RyanJsonCreateIntArray 传入 NULL 指针和 0 长度 + RyanJson_t nullIntArr = RyanJsonCreateIntArray(NULL, 0); + // 该场景属于边界输入:只要不崩溃且行为可预测即可。 + // 若实现返回空数组,则继续校验 size; + // 若实现返回 NULL,也视为可接受行为。 + if (nullIntArr) + { + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetArraySize(nullIntArr)); + RyanJsonDelete(nullIntArr); + } +} + +static void testCreatePropertyCheck(void) +{ + // 验证创建后的节点属性 + RyanJson_t node = RyanJsonCreateInt("age", 25); + TEST_ASSERT_NOT_NULL(node); + TEST_ASSERT_EQUAL_STRING("age", RyanJsonGetKey(node)); + TEST_ASSERT_TRUE(RyanJsonIsInt(node)); + TEST_ASSERT_EQUAL_INT(25, RyanJsonGetIntValue(node)); + RyanJsonDelete(node); + + node = RyanJsonCreateDouble("pi", 3.14159); + TEST_ASSERT_NOT_NULL(node); + TEST_ASSERT_TRUE(RyanJsonIsDouble(node)); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(3.14159, RyanJsonGetDoubleValue(node))); + RyanJsonDelete(node); + + node = RyanJsonCreateBool("active", RyanJsonTrue); + TEST_ASSERT_NOT_NULL(node); + TEST_ASSERT_TRUE(RyanJsonIsBool(node)); + TEST_ASSERT_EQUAL_INT(RyanJsonTrue, RyanJsonGetBoolValue(node)); + RyanJsonDelete(node); +} + +static void testAddBoundary(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + + // INT32 边界 + RyanJsonAddIntToObject(obj, "max", INT32_MAX); + RyanJsonAddIntToObject(obj, "min", INT32_MIN); + TEST_ASSERT_EQUAL_INT(INT32_MAX, RyanJsonGetIntValue(RyanJsonGetObjectToKey(obj, "max"))); + TEST_ASSERT_EQUAL_INT(INT32_MIN, RyanJsonGetIntValue(RyanJsonGetObjectToKey(obj, "min"))); + + // Double 边界(仅做数值路径校验,不做位级比较) + RyanJsonAddDoubleToObject(obj, "very_small", 1e-100); + RyanJsonAddDoubleToObject(obj, "very_big", 1e+100); + TEST_ASSERT_TRUE(RyanJsonIsDouble(RyanJsonGetObjectToKey(obj, "very_small"))); + TEST_ASSERT_TRUE(RyanJsonIsDouble(RyanJsonGetObjectToKey(obj, "very_big"))); + + // 空 key 添加测试 + RyanJsonAddBoolToObject(obj, "", RyanJsonTrue); + TEST_ASSERT_TRUE(RyanJsonGetBoolValue(RyanJsonGetObjectToKey(obj, ""))); + + RyanJsonDelete(obj); +} + +static void testCreateStandardComplexHierarchy(void) +{ + RyanJson_t jsonRoot, item; + + // 对象生成测试 + jsonRoot = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, "创建对象失败"); + + RyanJsonAddIntToObject(jsonRoot, "inter", 16); + RyanJsonAddDoubleToObject(jsonRoot, "double", 16.89); + RyanJsonAddStringToObject(jsonRoot, "string", "hello"); + RyanJsonAddBoolToObject(jsonRoot, "boolTrue", RyanJsonTrue); + RyanJsonAddBoolToObject(jsonRoot, "boolFalse", RyanJsonFalse); + RyanJsonAddNullToObject(jsonRoot, "null"); + + /** + * @brief 对象添加测试 + * + */ + item = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL_MESSAGE(item, "创建子对象失败"); + RyanJsonAddIntToObject(item, "inter", 16); + RyanJsonAddDoubleToObject(item, "double", 16.89); + RyanJsonAddStringToObject(item, "string", "hello"); + RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); + RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); + RyanJsonAddNullToObject(item, "null"); + RyanJsonAddItemToObject(jsonRoot, "item", item); + + /** + * @brief 数组添加测试 + * + */ + int32_t arrayInt[] = {16, 16, 16, 16, 16}; + RyanJsonAddItemToObject(jsonRoot, "arrayInt", RyanJsonCreateIntArray(arrayInt, sizeof(arrayInt) / sizeof(arrayInt[0]))); + + double arrayDouble[] = {16.89, 16.89, 16.89, 16.89, 16.89}; + RyanJsonAddItemToObject(jsonRoot, "arrayDouble", + RyanJsonCreateDoubleArray(arrayDouble, sizeof(arrayDouble) / sizeof(arrayDouble[0]))); + + const char *arrayString[] = {"hello", "hello", "hello", "hello", "hello"}; + RyanJsonAddItemToObject(jsonRoot, "arrayString", + RyanJsonCreateStringArray(arrayString, sizeof(arrayString) / sizeof(arrayString[0]))); + + RyanJson_t array = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL_MESSAGE(array, "创建数组失败"); + RyanJsonAddIntToArray(array, 16); + RyanJsonAddDoubleToArray(array, 16.89); + RyanJsonAddStringToArray(array, "hello"); + RyanJsonAddBoolToArray(array, RyanJsonTrue); + RyanJsonAddBoolToArray(array, RyanJsonFalse); + RyanJsonAddNullToArray(array); + RyanJsonAddItemToObject(jsonRoot, "array", array); + + /** + * @brief 对象数组测试 + * + */ + RyanJson_t arrayItem = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL_MESSAGE(arrayItem, "创建对象数组失败"); + + item = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL_MESSAGE(item, "创建对象数组项 1 失败"); + RyanJsonAddIntToObject(item, "inter", 16); + RyanJsonAddDoubleToObject(item, "double", 16.89); + RyanJsonAddStringToObject(item, "string", "hello"); + RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); + RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); + RyanJsonAddNullToObject(item, "null"); + RyanJsonAddItemToArray(arrayItem, item); + + item = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL_MESSAGE(item, "创建对象数组项 2 失败"); + RyanJsonAddIntToObject(item, "inter", 16); + RyanJsonAddDoubleToObject(item, "double", 16.89); + RyanJsonAddStringToObject(item, "string", "hello"); + RyanJsonAddBoolToObject(item, "boolTrue", RyanJsonTrue); + RyanJsonAddBoolToObject(item, "boolFalse", RyanJsonFalse); + RyanJsonAddNullToObject(item, "null"); + RyanJsonAddItemToArray(arrayItem, item); + + RyanJsonAddItemToObject(jsonRoot, "arrayItem", arrayItem); + + // 最终验证根对象结构 +#if true == RyanJsonDefaultAddAtHead + testCheckRootEx(jsonRoot, RyanJsonTrue); +#else + testCheckRootEx(jsonRoot, RyanJsonFalse); +#endif + + RyanJsonDelete(jsonRoot); +} + +static void testCreateHugeString(void) +{ + // 极限大字符串创建(模拟大对象;为控制测试耗时,先用 10KB 堆内存) + uint32_t len = 1024 * 10; + char *hugeStr = (char *)malloc(len + 1); + TEST_ASSERT_NOT_NULL(hugeStr); + memset(hugeStr, 'A', len); + hugeStr[len] = '\0'; + + RyanJson_t strJson = RyanJsonCreateString("huge", hugeStr); + TEST_ASSERT_NOT_NULL(strJson); + TEST_ASSERT_EQUAL_STRING(hugeStr, RyanJsonGetStringValue(strJson)); + + RyanJsonDelete(strJson); + free(hugeStr); + + // 特殊 Key 创建 + RyanJson_t nullKeyJson = RyanJsonCreateInt(NULL, 123); + TEST_ASSERT_NOT_NULL(nullKeyJson); // 允许 NULL key(常见于 Array item) + // 无 key 节点下,RyanJsonGetKey 可能返回值区指针。 + // 因此这里用 RyanJsonIsKey 作为权威判定。 + TEST_ASSERT_FALSE(RyanJsonIsKey(nullKeyJson)); + RyanJsonDelete(nullKeyJson); +} + +static void testCreateOom(void) +{ + createSetFailAfter(0); + RyanJson_t obj = RyanJsonCreateObject(); + createRestoreHooks(); + if (obj) { RyanJsonDelete(obj); } + TEST_ASSERT_NULL_MESSAGE(obj, "CreateObject OOM 应返回 NULL"); +} + +static void testInsertOutOfRangeAndKeyValidation(void) +{ + // Array:index 超出范围应追加到尾部 + RyanJson_t arr = RyanJsonCreateArray(); + RyanJsonAddIntToArray(arr, 1); + RyanJsonAddIntToArray(arr, 2); + TEST_ASSERT_TRUE(RyanJsonInsert(arr, 100, RyanJsonCreateInt(NULL, 3))); + TEST_ASSERT_EQUAL_INT(3, RyanJsonGetSize(arr)); + TEST_ASSERT_EQUAL_INT(3, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 2))); + RyanJsonDelete(arr); + + // Object:item 无 key 应失败 + RyanJson_t obj = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj, "a", 1); + RyanJson_t noKey = RyanJsonCreateInt(NULL, 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonInsert(obj, 0, noKey), "Object 插入无 key item 应失败"); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); + + // Object:重复 key 应失败 +#if true == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_FALSE_MESSAGE(RyanJsonInsert(obj, 0, RyanJsonCreateInt("a", 2)), "严格模式下 Object 插入重复 key 应失败"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonAddIntToObject(obj, "a", 3), "严格模式下 AddIntToObject 重复 key 应失败"); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); +#else + TEST_ASSERT_TRUE_MESSAGE(RyanJsonInsert(obj, 0, RyanJsonCreateInt("a", 2)), "非严格模式下 Object 插入重复 key 应成功"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddIntToObject(obj, "a", 3), "非严格模式下 AddIntToObject 重复 key 应成功"); + TEST_ASSERT_EQUAL_INT(3, RyanJsonGetSize(obj)); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(3, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); +#else + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); +#endif +#endif + RyanJsonDelete(obj); +} + +static void testInsertRejectAttachedItem(void) +{ + RyanJson_t arr1 = RyanJsonCreateArray(); + RyanJson_t arr2 = RyanJsonCreateArray(); + + RyanJson_t item = RyanJsonCreateInt(NULL, 1); + TEST_ASSERT_TRUE(RyanJsonInsert(arr1, 0, item)); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonInsert(arr2, 0, item), "已挂树的 item 不应再次插入"); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(arr1)); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(arr2)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr1, 0))); + + RyanJsonDelete(arr1); + RyanJsonDelete(arr2); +} + +static void testDetachedItemApi(void) +{ + TEST_ASSERT_FALSE(RyanJsonIsDetachedItem(NULL)); + + RyanJson_t arr = RyanJsonCreateArray(); + RyanJson_t item = RyanJsonCreateInt(NULL, 123); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(item)); + + TEST_ASSERT_TRUE(RyanJsonInsert(arr, 0, item)); + TEST_ASSERT_FALSE(RyanJsonIsDetachedItem(item)); + + RyanJson_t detached = RyanJsonDetachByIndex(arr, 0); + TEST_ASSERT_EQUAL_PTR(item, detached); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(detached)); + + // 人工篡改到“非法游离态”:next==NULL 但 IsLast==1,应判定为非游离节点 + RyanJsonSetPayloadIsLastByFlag(detached, RyanJsonTrue); + TEST_ASSERT_FALSE(RyanJsonIsDetachedItem(detached)); + // 恢复状态,避免影响后续释放 + RyanJsonSetPayloadIsLastByFlag(detached, RyanJsonFalse); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(detached)); + + RyanJsonDelete(detached); + RyanJsonDelete(arr); +} + +static void testAddLargeArrayStress(void) +{ + // 压力测试:添加大量元素 + RyanJson_t arr = RyanJsonCreateArray(); + int32_t count = 2000; // 2000 个元素 + for (int32_t i = 0; i < count; i++) + { + RyanJsonAddIntToArray(arr, i); + } + TEST_ASSERT_EQUAL_INT(count, RyanJsonGetArraySize(arr)); + + // 验证最后一个 + RyanJson_t last = RyanJsonGetObjectByIndex(arr, count - 1); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetIntValue(last)); +#else + TEST_ASSERT_EQUAL_INT(count - 1, RyanJsonGetIntValue(last)); +#endif + + RyanJsonDelete(arr); + + // 防御性测试:向非容器添加 + RyanJson_t strNode = RyanJsonCreateString("k", "v"); + // 尝试向 String 节点添加子项,预期失败或不崩溃 + RyanJson_t item = RyanJsonCreateInt("sub", 1); + RyanJsonBool_e res = RyanJsonInsert(strNode, 0, item); + TEST_ASSERT_FALSE(res); + + // 该场景 item 是游离节点,Insert 失败会负责释放 item + RyanJsonDelete(strNode); +} + +static void testAddItemObjectSemantics(void) +{ + // AddItemToObject 正常添加容器 + RyanJson_t obj = RyanJsonCreateObject(); + RyanJson_t childArr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_NOT_NULL(childArr); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(childArr, 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(childArr, 2)); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddItemToObject(obj, "arr", childArr), "AddItemToObject(容器) 应成功"); + + RyanJson_t arrNode = RyanJsonGetObjectByKey(obj, "arr"); + TEST_ASSERT_NOT_NULL(arrNode); + TEST_ASSERT_TRUE(RyanJsonIsArray(arrNode)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetArraySize(arrNode)); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arrNode, 0))); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arrNode, 1))); +#else + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arrNode, 0))); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arrNode, 1))); +#endif + + // AddItemToObject 重复 key(容器)行为由严格模式控制 + RyanJson_t dupObj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(dupObj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(dupObj, "k", 99)); +#if true == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_FALSE_MESSAGE(RyanJsonAddItemToObject(obj, "arr", dupObj), "严格模式下 AddItemToObject(重复 key) 应失败"); +#else + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddItemToObject(obj, "arr", dupObj), "非严格模式下 AddItemToObject(重复 key) 应成功"); +#endif + +#if true == RyanJsonStrictObjectKeyCheck + arrNode = RyanJsonGetObjectByKey(obj, "arr"); + TEST_ASSERT_NOT_NULL(arrNode); + TEST_ASSERT_TRUE(RyanJsonIsArray(arrNode)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetArraySize(arrNode)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); +#else + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(obj)); + + RyanJson_t firstArrKeyNode = RyanJsonGetObjectByIndex(obj, 0); + RyanJson_t secondArrKeyNode = RyanJsonGetObjectByIndex(obj, 1); + TEST_ASSERT_NOT_NULL(firstArrKeyNode); + TEST_ASSERT_NOT_NULL(secondArrKeyNode); + TEST_ASSERT_TRUE(RyanJsonIsKey(firstArrKeyNode)); + TEST_ASSERT_TRUE(RyanJsonIsKey(secondArrKeyNode)); + TEST_ASSERT_EQUAL_STRING("arr", RyanJsonGetKey(firstArrKeyNode)); + TEST_ASSERT_EQUAL_STRING("arr", RyanJsonGetKey(secondArrKeyNode)); + +#if true == RyanJsonDefaultAddAtHead + // 头插模式:新插入的 dupObj 在前,旧数组在后 + TEST_ASSERT_TRUE(RyanJsonIsObject(firstArrKeyNode)); + TEST_ASSERT_TRUE(RyanJsonIsArray(secondArrKeyNode)); + TEST_ASSERT_EQUAL_INT(99, RyanJsonGetIntValue(RyanJsonGetObjectByKey(firstArrKeyNode, "k"))); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetArraySize(secondArrKeyNode)); + + arrNode = RyanJsonGetObjectByKey(obj, "arr"); + TEST_ASSERT_NOT_NULL(arrNode); + TEST_ASSERT_TRUE(RyanJsonIsObject(arrNode)); +#else + // 尾插模式:旧数组在前,新插入的 dupObj 在后 + TEST_ASSERT_TRUE(RyanJsonIsArray(firstArrKeyNode)); + TEST_ASSERT_TRUE(RyanJsonIsObject(secondArrKeyNode)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetArraySize(firstArrKeyNode)); + TEST_ASSERT_EQUAL_INT(99, RyanJsonGetIntValue(RyanJsonGetObjectByKey(secondArrKeyNode, "k"))); + + arrNode = RyanJsonGetObjectByKey(obj, "arr"); + TEST_ASSERT_NOT_NULL(arrNode); + TEST_ASSERT_TRUE(RyanJsonIsArray(arrNode)); +#endif +#endif + + // AddItemToObject 传入标量应失败 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonAddItemToObject(obj, "scalar", RyanJsonCreateInt(NULL, 7)), "AddItemToObject(标量) 应失败"); + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(obj, "scalar")); + + // 非对象调用 AddItemToObject 应失败 + RyanJson_t notObj = RyanJsonCreateInt("num", 1); + RyanJson_t childObj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(notObj); + TEST_ASSERT_NOT_NULL(childObj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(childObj, "x", 1)); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonAddItemToObject(notObj, "child", childObj), "非对象调用 AddItemToObject 应失败"); + TEST_ASSERT_TRUE(RyanJsonIsInt(notObj)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(notObj)); + + RyanJsonDelete(notObj); + RyanJsonDelete(obj); +} + +void testCreateRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testCreateEdgeCases); + RUN_TEST(testCreatePropertyCheck); + RUN_TEST(testAddBoundary); + RUN_TEST(testCreateStandardComplexHierarchy); + RUN_TEST(testCreateHugeString); + RUN_TEST(testCreateOom); + RUN_TEST(testInsertOutOfRangeAndKeyValidation); + RUN_TEST(testInsertRejectAttachedItem); + RUN_TEST(testDetachedItemApi); + RUN_TEST(testAddLargeArrayStress); + RUN_TEST(testAddItemObjectSemantics); +} diff --git a/test/unityTest/cases/core/testDelete.c b/test/unityTest/cases/core/testDelete.c new file mode 100644 index 0000000..af5105e --- /dev/null +++ b/test/unityTest/cases/core/testDelete.c @@ -0,0 +1,268 @@ +#include "testBase.h" + +static void testDeleteEdgeCases(void) +{ + // 删除 NULL(应安全返回) + RyanJsonDelete(NULL); + + // RyanJsonDeleteByKey 参数校验 + RyanJson_t obj = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj, "a", 1); + + TEST_ASSERT_FALSE_MESSAGE(RyanJsonDeleteByKey(NULL, "a"), "DeleteByKey(NULL, key) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonDeleteByKey(obj, NULL), "DeleteByKey(obj, NULL) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonDeleteByKey(obj, "non_existent"), "DeleteByKey(不存在的key) 应返回 False"); + + // RyanJsonDeleteByIndex 参数校验 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonDeleteByIndex(NULL, 0), "DeleteByIndex(NULL, 0) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonDeleteByIndex(obj, 999), "DeleteByIndex(越界) 应返回 False"); + + // 从非容器类型删除 + RyanJson_t num = RyanJsonCreateInt("num", 123); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonDeleteByKey(num, "a"), "从非容器 DeleteByKey 应返回 False"); // 内部 Detach 会做类型与存在性检查 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonDeleteByIndex(num, 0), "从非容器 DeleteByIndex 应返回 False"); + + RyanJsonDelete(obj); + RyanJsonDelete(num); +} + +static void testDeleteMassiveItemsStress(void) +{ + // 连续删除直到空(Array) + RyanJson_t arr = RyanJsonCreateArray(); + for (int32_t i = 0; i < 100; i++) + { + RyanJsonAddIntToArray(arr, i); + } + TEST_ASSERT_EQUAL_INT(100, RyanJsonGetSize(arr)); + + // 从头部连续删除 + while (RyanJsonGetSize(arr) > 0) + { + TEST_ASSERT_TRUE(RyanJsonDeleteByIndex(arr, 0)); + } + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(arr)); + + // 空数组删除 + TEST_ASSERT_FALSE(RyanJsonDeleteByIndex(arr, 0)); + RyanJsonDelete(arr); + + // 连续删除直到空(Object) + RyanJson_t obj = RyanJsonCreateObject(); + char key[16]; + for (int32_t i = 0; i < 100; i++) + { + snprintf(key, sizeof(key), "%d", i); + RyanJsonAddIntToObject(obj, key, i); + } + TEST_ASSERT_EQUAL_INT(100, RyanJsonGetSize(obj)); + + // 从头部连续删除(Object 也是链表,ByIndex=0 有效) + while (RyanJsonGetSize(obj) > 0) + { + TEST_ASSERT_TRUE(RyanJsonDeleteByIndex(obj, 0)); + } + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(obj)); + + // 对象按 key 全量删除 + for (int32_t i = 0; i < 100; i++) + { + snprintf(key, sizeof(key), "%d", i); + RyanJsonAddIntToObject(obj, key, i); + } + for (int32_t i = 0; i < 100; i++) + { + snprintf(key, sizeof(key), "%d", i); + TEST_ASSERT_TRUE(RyanJsonDeleteByKey(obj, key)); + } + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(obj)); + + // 空 Object 删除不存在的 key + TEST_ASSERT_FALSE(RyanJsonDeleteByKey(obj, "any")); + RyanJsonDelete(obj); +} + +static void testDeleteSingleNodeAndReuse(void) +{ + // Object:删除唯一节点后应保持可复用 + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "a", 1)); + TEST_ASSERT_TRUE(RyanJsonDeleteByKey(obj, "a")); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(obj)); + TEST_ASSERT_NULL(RyanJsonGetObjectValue(obj)); + TEST_ASSERT_FALSE(RyanJsonDeleteByKey(obj, "a")); + + TEST_ASSERT_TRUE(RyanJsonAddStringToObject(obj, "b", "ok")); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); + TEST_ASSERT_EQUAL_STRING("ok", RyanJsonGetStringValue(RyanJsonGetObjectByKey(obj, "b"))); + + // Array:删除唯一节点后应保持可复用 + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 9)); + TEST_ASSERT_TRUE(RyanJsonDeleteByIndex(arr, 0)); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(arr)); + TEST_ASSERT_NULL(RyanJsonGetObjectValue(arr)); + TEST_ASSERT_FALSE(RyanJsonDeleteByIndex(arr, 0)); + + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 10)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(arr)); + TEST_ASSERT_EQUAL_INT(10, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 0))); + + RyanJsonDelete(arr); + RyanJsonDelete(obj); +} + +static void testDeleteTailAndMiddleThenAppend(void) +{ + // Object:删除尾和中间节点后再次追加,验证链表仍可正常维护 + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "a", 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "b", 2)); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "c", 3)); + + TEST_ASSERT_TRUE(RyanJsonDeleteByKey(obj, "c")); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(obj)); + TEST_ASSERT_TRUE(RyanJsonDeleteByKey(obj, "b")); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); + + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "d", 4)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(obj)); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(4, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(obj, 0))); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(obj, 1))); +#else + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(obj, 0))); + TEST_ASSERT_EQUAL_INT(4, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(obj, 1))); +#endif + + // Array:同样覆盖“删尾/删中后再追加”路径 + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 10)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 20)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 30)); + + TEST_ASSERT_TRUE(RyanJsonDeleteByIndex(arr, 2)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(arr)); + TEST_ASSERT_TRUE(RyanJsonDeleteByIndex(arr, 1)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(arr)); + + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 40)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(arr)); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(40, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 0))); + TEST_ASSERT_EQUAL_INT(30, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 1))); +#else + TEST_ASSERT_EQUAL_INT(10, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 0))); + TEST_ASSERT_EQUAL_INT(40, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 1))); +#endif + + RyanJsonDelete(arr); + RyanJsonDelete(obj); +} + +static void testDeleteStandardOperations(void) +{ + + // 保持原始 jsonStr,不做修改 + char jsonstr[] = + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"]," + "\"array\":[16,16.89,\"hello\",true,false,null]," + "\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]," + "\"string2222\":\"hello\"}"; + + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Json 失败"); + + /** + * @brief 删除对象中的节点(头、中、尾) + */ + // 删除中间节点(double) + RyanJsonDeleteByKey(json, "double"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "double"), "删除中间节点 double 失败"); + + // 删除头部节点(inter) + RyanJsonDeleteByIndex(json, 0); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "inter"), "删除头部节点 inter 失败"); + + // 删除尾部节点(string2222) + uint32_t lastIndex = RyanJsonGetSize(json) - 1; + RyanJsonDeleteByIndex(json, lastIndex); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "string2222"), "删除尾部节点 string2222 失败"); + + /** + * @brief 删除数组中的元素(arrayInt) + */ + RyanJson_t array = RyanJsonGetObjectToKey(json, "arrayInt"); + TEST_ASSERT_NOT_NULL_MESSAGE(array, "获取 arrayInt 失败"); + + // 删除数组首位 + RyanJsonDeleteByIndex(array, 0); + TEST_ASSERT_EQUAL_INT_MESSAGE(4, RyanJsonGetSize(array), "删除数组首位后长度错误"); + + // 删除数组中间元素 + RyanJsonDeleteByIndex(array, 1); + TEST_ASSERT_EQUAL_INT_MESSAGE(3, RyanJsonGetSize(array), "删除数组中间元素后长度错误"); + + // 删除数组尾部元素 + lastIndex = RyanJsonGetSize(array) - 1; + RyanJsonDeleteByIndex(array, lastIndex); + TEST_ASSERT_EQUAL_INT_MESSAGE(2, RyanJsonGetSize(array), "删除数组尾部元素后长度错误"); + + /** + * @brief 深层嵌套删除(item) + */ + RyanJsonDeleteByKey(json, "item"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "item"), "删除嵌套对象 item 失败"); + + /** + * @brief 数组对象元素删除(arrayItem) + */ + RyanJson_t arrObj = RyanJsonGetObjectToKey(json, "arrayItem"); + TEST_ASSERT_NOT_NULL_MESSAGE(arrObj, "获取 arrayItem 失败"); + + // 删除第一个对象 + RyanJsonDeleteByIndex(arrObj, 0); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, RyanJsonGetSize(arrObj), "删除数组首个对象后长度错误"); + + // 删除最后一个对象 + RyanJsonDeleteByIndex(arrObj, 0); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, RyanJsonGetSize(arrObj), "删除数组最后一个对象后长度错误"); + + /** + * @brief 特殊类型删除(null / bool) + */ + RyanJsonDeleteByKey(json, "null"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "null"), "删除 null 节点失败"); + + RyanJsonDeleteByKey(json, "boolTrue"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "boolTrue"), "删除 boolTrue 节点失败"); + + RyanJsonDeleteByKey(json, "boolFalse"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "boolFalse"), "删除 boolFalse 节点失败"); + + /** + * @brief 异常路径覆盖(健壮性) + */ + RyanJsonDeleteByKey(json, "non_exist"); // 删除不存在的 key + RyanJsonDeleteByIndex(NULL, 0); // 在 NULL 上操作 + + RyanJsonDelete(json); +} + +void testDeleteRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testDeleteEdgeCases); + RUN_TEST(testDeleteSingleNodeAndReuse); + RUN_TEST(testDeleteTailAndMiddleThenAppend); + RUN_TEST(testDeleteStandardOperations); + RUN_TEST(testDeleteMassiveItemsStress); +} diff --git a/test/unityTest/cases/core/testDetach.c b/test/unityTest/cases/core/testDetach.c new file mode 100644 index 0000000..b1fda5b --- /dev/null +++ b/test/unityTest/cases/core/testDetach.c @@ -0,0 +1,423 @@ +#include "testBase.h" + +static void testDetachEdgeCases(void) +{ + // 分离接口输入 NULL + TEST_ASSERT_NULL_MESSAGE(RyanJsonDetachByKey(NULL, "key"), "DetachByKey(NULL, key) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonDetachByIndex(NULL, 0), "DetachByIndex(NULL, 0) 应返回 NULL"); + + // 分离不存在的元素 + RyanJson_t obj = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj, "a", 1); + + TEST_ASSERT_NULL_MESSAGE(RyanJsonDetachByKey(obj, "non_existent"), "DetachByKey(不存在) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonDetachByIndex(obj, 99), "DetachByIndex(越界) 应返回 NULL"); + + // 从非容器节点分离 + RyanJson_t val = RyanJsonCreateString("str", "value"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonDetachByKey(val, "a"), "从 String DetachByKey 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonDetachByIndex(val, 0), "从 String DetachByIndex 应返回 NULL"); + + RyanJsonDelete(obj); + RyanJsonDelete(val); +} + +static void testDetachDuplicateKey(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "dup", 1)); +#if true == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_FALSE_MESSAGE(RyanJsonAddIntToObject(obj, "dup", 2), "严格模式下对象不应允许重复 key"); +#else + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddIntToObject(obj, "dup", 2), "非严格模式下对象应允许重复 key"); +#endif + + RyanJson_t only = RyanJsonGetObjectByKey(obj, "dup"); + TEST_ASSERT_NOT_NULL(only); +#if true == RyanJsonDefaultAddAtHead && false == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(only)); +#else + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(only)); +#endif + + RyanJson_t detached = RyanJsonDetachByKey(obj, "dup"); + TEST_ASSERT_NOT_NULL(detached); +#if true == RyanJsonDefaultAddAtHead && false == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(detached)); +#else + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(detached)); +#endif + RyanJsonDelete(detached); + +#if true == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(obj, "dup")); +#else + RyanJson_t second = RyanJsonGetObjectByKey(obj, "dup"); + TEST_ASSERT_NOT_NULL(second); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(second)); +#else + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(second)); +#endif + RyanJson_t detached2 = RyanJsonDetachByKey(obj, "dup"); + TEST_ASSERT_NOT_NULL(detached2); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(detached2)); +#else + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(detached2)); +#endif + RyanJsonDelete(detached2); + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(obj, "dup")); +#endif + + RyanJsonDelete(obj); +} + +static void testDetachCrossObject(void) +{ + // 从一个 Object 分离节点并迁移到另一个 Object + RyanJson_t obj1 = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj1, "move_me", 100); + + RyanJson_t obj2 = RyanJsonCreateObject(); + + RyanJson_t item = RyanJsonDetachByKey(obj1, "move_me"); + TEST_ASSERT_NOT_NULL(item); + TEST_ASSERT_EQUAL_INT(100, RyanJsonGetIntValue(item)); + + // 迁移到 obj2 + // 注意:RyanJsonAddItemToObject 会创建包装节点,可能引入额外层级。 + // 这里使用 ChangeKey + Insert,表达“移动并重命名”的语义。 + RyanJsonChangeKey(item, "moved"); + RyanJsonInsert(obj2, UINT32_MAX, item); + + TEST_ASSERT_EQUAL_INT(100, RyanJsonGetIntValue(RyanJsonGetObjectToKey(obj2, "moved"))); + + RyanJsonDelete(obj1); + RyanJsonDelete(obj2); +} + +static void testDetachReInsert(void) +{ + // 分离 -> 修改 -> 重新插入 + RyanJson_t arr = RyanJsonCreateArray(); + RyanJsonAddIntToArray(arr, 10); + + RyanJson_t item = RyanJsonDetachByIndex(arr, 0); + TEST_ASSERT_NOT_NULL(item); + TEST_ASSERT_EQUAL_INT(10, RyanJsonGetIntValue(item)); + + RyanJsonAddIntToArray(arr, 20); // 当前数组为 [20] + + // 使用 RyanJsonInsert 直接插入,避免 RyanJsonAddItemToArray 的包装行为 + RyanJsonInsert(arr, UINT32_MAX, item); // 当前数组为 [20, 10] + + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetArraySize(arr)); + TEST_ASSERT_EQUAL_INT(20, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 0))); + TEST_ASSERT_EQUAL_INT(10, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 1))); + + RyanJsonDelete(arr); +} + +static void testDetachSingleNodeAndReuse(void) +{ + // Object:单节点分离后应变为空 Object,并可重新插回 + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "only", 11)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); + + RyanJson_t detachedObjItem = RyanJsonDetachByKey(obj, "only"); + TEST_ASSERT_NOT_NULL(detachedObjItem); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(detachedObjItem)); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(obj)); + TEST_ASSERT_NULL(RyanJsonGetObjectValue(obj)); + TEST_ASSERT_NULL(RyanJsonDetachByKey(obj, "only")); + + TEST_ASSERT_TRUE(RyanJsonInsert(obj, 0, detachedObjItem)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); + TEST_ASSERT_EQUAL_INT(11, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "only"))); + + // Array:单节点分离后应变为空 Array,并可重新插回 + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 22)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(arr)); + + RyanJson_t detachedArrItem = RyanJsonDetachByIndex(arr, 0); + TEST_ASSERT_NOT_NULL(detachedArrItem); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(detachedArrItem)); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(arr)); + TEST_ASSERT_NULL(RyanJsonGetObjectValue(arr)); + TEST_ASSERT_NULL(RyanJsonDetachByIndex(arr, 0)); + + TEST_ASSERT_TRUE(RyanJsonInsert(arr, UINT32_MAX, detachedArrItem)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(arr)); + TEST_ASSERT_EQUAL_INT(22, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 0))); + + RyanJsonDelete(obj); + RyanJsonDelete(arr); +} + +static void testDetachTailAndMiddleThenAppend(void) +{ + // Object:先分离尾节点,再分离中间节点,最后追加新节点,验证链表修复正确 + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "a", 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "b", 2)); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "c", 3)); + + RyanJson_t detachedTail = RyanJsonDetachByKey(obj, "c"); + TEST_ASSERT_NOT_NULL(detachedTail); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(detachedTail)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(obj)); + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(obj, "c")); + RyanJsonDelete(detachedTail); + + RyanJson_t detachedMiddle = RyanJsonDetachByKey(obj, "b"); + TEST_ASSERT_NOT_NULL(detachedMiddle); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(detachedMiddle)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(obj, "b")); + RyanJsonDelete(detachedMiddle); + + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "d", 4)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(obj)); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(4, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(obj, 0))); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(obj, 1))); +#else + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(obj, 0))); + TEST_ASSERT_EQUAL_INT(4, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(obj, 1))); +#endif + + // Array:同样覆盖“尾/中分离后再追加”的路径 + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 10)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 20)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 30)); + + RyanJson_t arrTail = RyanJsonDetachByIndex(arr, 2); + TEST_ASSERT_NOT_NULL(arrTail); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(arrTail)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(arr)); + RyanJsonDelete(arrTail); + + RyanJson_t arrMiddle = RyanJsonDetachByIndex(arr, 1); + TEST_ASSERT_NOT_NULL(arrMiddle); + TEST_ASSERT_TRUE(RyanJsonIsDetachedItem(arrMiddle)); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(arr)); + RyanJsonDelete(arrMiddle); + + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 40)); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetSize(arr)); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(40, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 0))); + TEST_ASSERT_EQUAL_INT(30, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 1))); +#else + TEST_ASSERT_EQUAL_INT(10, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 0))); + TEST_ASSERT_EQUAL_INT(40, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 1))); +#endif + + RyanJsonDelete(arr); + RyanJsonDelete(obj); +} + +static void testDetachStandardOperations(void) +{ + + char jsonstr[] = + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"]," + "\"array\":[16,16.89,\"hello\",true,false,null]," + "\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{" + "\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16," + "\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89," + "\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]," + "\"string2222\":\"hello\"}"; + + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Json 失败"); + + /** + * @brief 对象子项分离测试(头、中、尾) + */ + { + // 头部(第一个 key:inter) + RyanJson_t detached = RyanJsonDetachByIndex(json, 0); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离头部项 inter 失败"); + RyanJsonDelete(detached); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "inter"), "分离后 inter 依然存在"); + + // 中间(double) + detached = RyanJsonDetachByKey(json, "double"); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离中间项 double 失败"); + RyanJsonDelete(detached); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "double"), "分离后 double 依然存在"); + + // 尾部(最后一个 key:string2222) + uint32_t lastIndex = RyanJsonGetSize(json) - 1; + detached = RyanJsonDetachByIndex(json, lastIndex); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离尾部项 string2222 失败"); + RyanJsonDelete(detached); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "string2222"), "分离后 string2222 依然存在"); + } + + /** + * @brief 数组元素分离测试 (arrayInt / arrayDouble / arrayString) + */ + { + RyanJson_t arrInt = RyanJsonGetObjectByKey(json, "arrayInt"); + TEST_ASSERT_NOT_NULL_MESSAGE(arrInt, "获取 arrayInt 失败"); + uint32_t size = RyanJsonGetSize(arrInt); + RyanJson_t detached = RyanJsonDetachByIndex(arrInt, 0); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离 arrayInt 头部项失败"); + RyanJsonDelete(detached); + TEST_ASSERT_EQUAL_INT_MESSAGE(size - 1, RyanJsonGetSize(arrInt), "分离 arrayInt 头部后长度未减少"); + + // 中间 + size = RyanJsonGetSize(arrInt); + detached = RyanJsonDetachByIndex(arrInt, 1); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离 arrayInt 中间项失败"); + RyanJsonDelete(detached); + TEST_ASSERT_EQUAL_INT_MESSAGE(size - 1, RyanJsonGetSize(arrInt), "分离 arrayInt 中间项后长度未减少"); + + // 尾部 + size = RyanJsonGetSize(arrInt); + detached = RyanJsonDetachByIndex(arrInt, size - 1); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离 arrayInt 尾部项失败"); + RyanJsonDelete(detached); + TEST_ASSERT_EQUAL_INT_MESSAGE(size - 1, RyanJsonGetSize(arrInt), "分离 arrayInt 尾部后长度未减少"); + } + + { + RyanJson_t arrDouble = RyanJsonGetObjectByKey(json, "arrayDouble"); + TEST_ASSERT_NOT_NULL_MESSAGE(arrDouble, "获取 arrayDouble 失败"); + uint32_t size = RyanJsonGetSize(arrDouble); + RyanJsonDelete(RyanJsonDetachByIndex(arrDouble, 0)); + TEST_ASSERT_EQUAL_INT_MESSAGE(size - 1, RyanJsonGetSize(arrDouble), "分离 arrayDouble 头部后长度未减少"); + } + + { + RyanJson_t arrString = RyanJsonGetObjectByKey(json, "arrayString"); + TEST_ASSERT_NOT_NULL_MESSAGE(arrString, "获取 arrayString 失败"); + uint32_t size = RyanJsonGetSize(arrString); + RyanJsonDelete(RyanJsonDetachByIndex(arrString, size - 1)); + TEST_ASSERT_EQUAL_INT_MESSAGE(size - 1, RyanJsonGetSize(arrString), "分离 arrayString 尾部后长度未减少"); + } + + /** + * @brief 嵌套对象分离测试 (item) + */ + { + RyanJson_t detached = RyanJsonDetachByKey(json, "item"); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离嵌套对象 item 失败"); + RyanJsonDelete(detached); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "item"), "分离后 item 依然存在"); + } + + /** + * @brief 数组对象元素分离测试 (arrayItem 头、中、尾) + */ + { + RyanJson_t arr = RyanJsonGetObjectByKey(json, "arrayItem"); + TEST_ASSERT_NOT_NULL_MESSAGE(arr, "获取 arrayItem 失败"); + + uint32_t size = RyanJsonGetSize(arr); + // 头部 + RyanJson_t detached = RyanJsonDetachByIndex(arr, 0); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离对象数组头部项失败"); + RyanJsonDelete(detached); + TEST_ASSERT_EQUAL_INT_MESSAGE(size - 1, RyanJsonGetSize(arr), "分离对象数组头部后长度未减少"); + + // 中间 + size = RyanJsonGetSize(arr); + detached = RyanJsonDetachByIndex(arr, 1); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离对象数组中间项失败"); + RyanJsonDelete(detached); + TEST_ASSERT_EQUAL_INT_MESSAGE(size - 1, RyanJsonGetSize(arr), "分离对象数组中间后长度未减少"); + + // 尾部 + size = RyanJsonGetSize(arr); + detached = RyanJsonDetachByIndex(arr, RyanJsonGetSize(arr) - 1); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离对象数组尾部项失败"); + RyanJsonDelete(detached); + TEST_ASSERT_EQUAL_INT_MESSAGE(size - 1, RyanJsonGetSize(arr), "分离对象数组尾部后长度未减少"); + } + + /** + * @brief 特殊类型分离测试(null / bool) + */ + { + RyanJson_t detached = RyanJsonDetachByKey(json, "null"); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离 null 失败"); + RyanJsonDelete(detached); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "null"), "分离后 null 依然存在"); + + detached = RyanJsonDetachByKey(json, "boolTrue"); + TEST_ASSERT_NOT_NULL_MESSAGE(detached, "分离 boolTrue 失败"); + RyanJsonDelete(detached); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(json, "boolTrue"), "分离后 boolTrue 依然存在"); + } + + RyanJsonDelete(json); +} + +static void testDetachMassiveItemsStress(void) +{ + // 循环创建并分离节点,验证内存稳定 + int32_t count = 100; + RyanJson_t arr = RyanJsonCreateArray(); + for (int32_t i = 0; i < count; i++) + { + RyanJsonAddIntToArray(arr, i); + } + + // 这里采用“每次分离 index=0”的策略,避免索引迁移带来的复杂性 + for (int32_t i = 0; i < count; i++) + { + RyanJson_t item = RyanJsonDetachByIndex(arr, 0); + TEST_ASSERT_NOT_NULL(item); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(count - 1 - i, RyanJsonGetIntValue(item)); +#else + TEST_ASSERT_EQUAL_INT(i, RyanJsonGetIntValue(item)); +#endif + RyanJsonDelete(item); + } + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetArraySize(arr)); + RyanJsonDelete(arr); + + // 分离刚添加的对象字段(字段类型为 String) + RyanJson_t obj = RyanJsonCreateObject(); + // 使用标准 AddString 宏:会创建带 key 的 String 节点并插入 + RyanJsonAddStringToObject(obj, "sub", "v"); + + RyanJson_t detached = RyanJsonDetachByKey(obj, "sub"); + TEST_ASSERT_NOT_NULL(detached); + TEST_ASSERT_EQUAL_STRING("v", RyanJsonGetStringValue(detached)); + + // 再次分离应返回 NULL + TEST_ASSERT_NULL(RyanJsonDetachByKey(obj, "sub")); + + RyanJsonDelete(detached); + RyanJsonDelete(obj); +} + +void testDetachRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testDetachEdgeCases); + RUN_TEST(testDetachDuplicateKey); + RUN_TEST(testDetachCrossObject); + RUN_TEST(testDetachReInsert); + RUN_TEST(testDetachSingleNodeAndReuse); + RUN_TEST(testDetachTailAndMiddleThenAppend); + RUN_TEST(testDetachStandardOperations); + RUN_TEST(testDetachMassiveItemsStress); +} diff --git a/test/unityTest/cases/core/testDuplicate.c b/test/unityTest/cases/core/testDuplicate.c new file mode 100644 index 0000000..4215a30 --- /dev/null +++ b/test/unityTest/cases/core/testDuplicate.c @@ -0,0 +1,198 @@ +#include "testBase.h" + +static void testDuplicateEdgeCases(void) +{ + // 复制 NULL + TEST_ASSERT_NULL_MESSAGE(RyanJsonDuplicate(NULL), "Duplicate(NULL) 应返回 NULL"); + + // 深拷贝验证 + // 创建一个嵌套对象: root -> child -> val + RyanJson_t root = RyanJsonCreateObject(); + RyanJson_t child = RyanJsonCreateObject(); + RyanJsonAddIntToObject(child, "val", 100); + RyanJsonAddItemToObject(root, "child", child); + + // 复制整个树 + RyanJson_t rootCopy = RyanJsonDuplicate(root); + TEST_ASSERT_NOT_NULL(rootCopy); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(root, rootCopy), "复制后内容应一致"); + + // 修改副本的深层值 + RyanJson_t childCopy = RyanJsonGetObjectToKey(rootCopy, "child"); + RyanJsonChangeIntValue(RyanJsonGetObjectToKey(childCopy, "val"), 200); + + // 验证:原件应保持 100,副本为 200 + TEST_ASSERT_EQUAL_INT_MESSAGE(100, RyanJsonGetIntValue(RyanJsonGetObjectToKey(root, "child", "val")), "修改副本不应影响原件"); + TEST_ASSERT_EQUAL_INT_MESSAGE(200, RyanJsonGetIntValue(RyanJsonGetObjectToKey(rootCopy, "child", "val")), "副本修改失效"); + + RyanJsonDelete(root); + RyanJsonDelete(rootCopy); +} + +static void testDuplicateEmptyAndSpecial(void) +{ + // 复制空对象和空数组 + RyanJson_t emptyObj = RyanJsonCreateObject(); + RyanJson_t dupEmptyObj = RyanJsonDuplicate(emptyObj); + TEST_ASSERT_NOT_NULL(dupEmptyObj); + TEST_ASSERT_TRUE(RyanJsonIsObject(dupEmptyObj)); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(dupEmptyObj)); + RyanJsonDelete(emptyObj); + RyanJsonDelete(dupEmptyObj); + + RyanJson_t emptyArr = RyanJsonCreateArray(); + RyanJson_t dupEmptyArr = RyanJsonDuplicate(emptyArr); + TEST_ASSERT_NOT_NULL(dupEmptyArr); + TEST_ASSERT_TRUE(RyanJsonIsArray(dupEmptyArr)); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetSize(dupEmptyArr)); + RyanJsonDelete(emptyArr); + RyanJsonDelete(dupEmptyArr); + + // 复制包含特殊值的对象 + RyanJson_t specialObj = RyanJsonCreateObject(); + RyanJsonAddNullToObject(specialObj, "null"); + RyanJsonAddBoolToObject(specialObj, "true", RyanJsonTrue); + RyanJsonAddBoolToObject(specialObj, "false", RyanJsonFalse); + RyanJsonAddStringToObject(specialObj, "emptyStr", ""); + + RyanJson_t dupSpecial = RyanJsonDuplicate(specialObj); + TEST_ASSERT_TRUE(RyanJsonCompare(specialObj, dupSpecial)); + RyanJsonDelete(specialObj); + RyanJsonDelete(dupSpecial); +} + +static void testDuplicateFullScenarios(void) +{ + RyanJson_t json, dupItem, jsonRoot = NULL; + char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" + "{\"inter\":16,\"double\":16." + "89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," + "16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," + "\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null}]}"; + + /** + * @brief 普通类型复制测试 + */ + json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Json 失败"); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "inter")); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(dupItem, RyanJsonGetObjectToKey(json, "inter")), "普通类型复制后比较失败"); + RyanJsonDelete(dupItem); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "inter")); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonAddItemToObject(json, "test", dupItem), "AddItemToObject 不应接受标量 item"); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "inter")); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonAddItemToObject(json, "test", dupItem), "AddItemToObject 不应接受标量 item"); + RyanJsonDelete(json); + + /** + * @brief 对象类型复制测试 + */ + json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "重新解析 Json 失败"); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(dupItem, RyanJsonGetObjectToKey(json, "item")), "对象类型复制后比较失败"); + RyanJsonDelete(dupItem); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); + RyanJsonAddItemToObject(json, "test", dupItem); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "item")), + "对象类型复制并添加后比较失败"); + RyanJsonDelete(RyanJsonDetachByKey(json, "test")); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); + RyanJsonAddItemToObject(json, "test", dupItem); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "item")), + "对象类型复制并添加后删除再添加比较失败"); + RyanJsonDelete(json); + + /** + * @brief 数组类型复制测试 + */ + json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "重新解析 Json 失败 (数组测试)"); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "arrayItem")); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(dupItem, RyanJsonGetObjectToKey(json, "arrayItem")), "数组类型复制后比较失败"); + RyanJsonDelete(dupItem); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "arrayItem")); + RyanJsonAddItemToObject(json, "test", dupItem); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "arrayItem")), + "数组类型复制并添加后比较失败"); + RyanJsonDelete(RyanJsonDetachByKey(json, "test")); + + dupItem = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "arrayItem")); + RyanJsonAddItemToObject(json, "test", dupItem); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(RyanJsonGetObjectToKey(json, "test"), RyanJsonGetObjectToKey(json, "arrayItem")), + "数组类型复制并添加后删除再添加比较失败"); + RyanJsonDelete(json); + + /** + * @brief 循环压力与内存泄漏测试 + */ + json = RyanJsonParse(jsonstr); + jsonRoot = RyanJsonCreateObject(); + RyanJsonAddBoolToObject(jsonRoot, "arrayItem", RyanJsonTrue); + + int32_t initialUse = vallocGetUse(); + for (uint8_t i = 0; i < 10; i++) + { + dupItem = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(dupItem, "循环中解析失败"); + + RyanJsonReplaceByKey(jsonRoot, "arrayItem", RyanJsonDuplicate(dupItem)); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(RyanJsonGetObjectToKey(jsonRoot, "arrayItem"), dupItem), "循环中替换并比较失败"); + + RyanJsonReplaceByKey(json, "arrayItem", RyanJsonDuplicate(RyanJsonGetObjectByKey(dupItem, "item"))); + TEST_ASSERT_TRUE_MESSAGE( + RyanJsonCompare(RyanJsonGetObjectToKey(json, "arrayItem"), RyanJsonGetObjectByKey(dupItem, "item")), + "循环中嵌套替换并比较失败"); + + RyanJsonDelete(dupItem); + + if (i > 0) { TEST_ASSERT_EQUAL_INT_MESSAGE(initialUse, vallocGetUse(), "内存泄漏检测失败"); } + initialUse = vallocGetUse(); + } + + RyanJsonDelete(json); + RyanJsonDelete(jsonRoot); +} + +static void testDuplicateMassiveStress(void) +{ + // 压力测试:大数组复制 + int32_t bigSize = 2000; + RyanJson_t bigArr = RyanJsonCreateArray(); + for (int32_t i = 0; i < bigSize; i++) + { + RyanJsonAddIntToArray(bigArr, i); + } + RyanJson_t dupBigArr = RyanJsonDuplicate(bigArr); + TEST_ASSERT_EQUAL_INT(bigSize, RyanJsonGetArraySize(dupBigArr)); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(dupBigArr, bigSize - 1))); +#else + TEST_ASSERT_EQUAL_INT(bigSize - 1, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(dupBigArr, bigSize - 1))); +#endif + RyanJsonDelete(bigArr); + RyanJsonDelete(dupBigArr); +} + +void testDuplicateRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testDuplicateEdgeCases); + RUN_TEST(testDuplicateEmptyAndSpecial); + RUN_TEST(testDuplicateFullScenarios); + RUN_TEST(testDuplicateMassiveStress); +} diff --git a/test/unityTest/cases/core/testForEach.c b/test/unityTest/cases/core/testForEach.c new file mode 100644 index 0000000..c22c892 --- /dev/null +++ b/test/unityTest/cases/core/testForEach.c @@ -0,0 +1,105 @@ +#include "testBase.h" + +static void testForEachEdgeCases(void) +{ + RyanJson_t item = NULL; + + // 遍历 NULL 对象 (应该安全跳过循环) + int32_t count = 0; + RyanJsonArrayForEach(NULL, item) + { + count++; + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, count, "遍历 NULL Array 应不执行循环"); + + count = 0; + RyanJsonObjectForEach(NULL, item) + { + count++; + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, count, "遍历 NULL Object 应不执行循环"); + + // 遍历非容器对象 (应该同上) + RyanJson_t num = RyanJsonCreateInt("num", 1); + count = 0; + RyanJsonArrayForEach(num, item) + { + count++; + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, count, "遍历非容器 Array 应不执行循环"); + + count = 0; + RyanJsonObjectForEach(num, item) + { + count++; + } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, count, "遍历非容器 Object 应不执行循环"); + RyanJsonDelete(num); + + // 循环中断测试 (break) + RyanJson_t arr = RyanJsonCreateArray(); + RyanJsonAddIntToArray(arr, 1); + RyanJsonAddIntToArray(arr, 2); + RyanJsonAddIntToArray(arr, 3); + + count = 0; + RyanJsonArrayForEach(arr, item) + { + count++; + if (RyanJsonGetIntValue(item) == 2) { break; } + } + TEST_ASSERT_EQUAL_INT_MESSAGE(2, count, "循环 break 测试失败"); + RyanJsonDelete(arr); +} + +static void testForEachIterativeTraversals(void) +{ + char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" + "{\"inter\":16,\"double\":16." + "89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," + "16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," + "\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null}]}"; + + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Json 失败"); + + RyanJson_t item = NULL; + + // 遍历 arrayDouble 数组测试 + RyanJsonArrayForEach(RyanJsonGetObjectToKey(json, "arrayDouble"), item) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(item), "数组元素不是浮点数类型"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareDouble(16.89, RyanJsonGetDoubleValue(item)), "数组元素值不正确"); + } + + // 遍历 arrayInt 数组测试 + RyanJsonArrayForEach(RyanJsonGetObjectToKey(json, "arrayInt"), item) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(item), "数组元素不是整数类型"); + TEST_ASSERT_EQUAL_INT_MESSAGE(16, RyanJsonGetIntValue(item), "数组元素值不正确"); + } + + // 遍历 item 对象测试 + RyanJsonObjectForEach(RyanJsonGetObjectToKey(json, "item"), item) + { + TEST_ASSERT_NOT_NULL_MESSAGE(RyanJsonGetKey(item), "对象键值为空"); + char *str = RyanJsonPrint(item, 128, RyanJsonTrue, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(str, "遍历项打印失败"); + RyanJsonFree(str); + } + + RyanJsonDelete(json); +} + +void testForEachRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testForEachEdgeCases); + RUN_TEST(testForEachIterativeTraversals); +} diff --git a/test/unityTest/cases/core/testLoadFailure.c b/test/unityTest/cases/core/testLoadFailure.c new file mode 100644 index 0000000..3f0dcb7 --- /dev/null +++ b/test/unityTest/cases/core/testLoadFailure.c @@ -0,0 +1,301 @@ +#include "testBase.h" + +static void testLoadFailureNullAndWhitespace(void) +{ + // NULL 和空字符串 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse(NULL), "Parse(NULL) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse(""), "Parse(\"\") 应返回 NULL"); + + // 仅有空白字符 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse(" "), "Parse(只含空格) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("\t\r\n"), "Parse(只含换行符) 应返回 NULL"); +} + +static void testLoadFailureTruncated(void) +{ + // 畸形 Json(缺少闭合) + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("[1, 2, 3"), "Parse(未闭合数组) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\": 1"), "Parse(未闭合对象) 应返回 NULL"); + + // 截断的输入 (非正常结束) + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{"), "截断的对象应解析失败"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("["), "截断的数组应解析失败"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":"), "缺少值的对象应解析失败"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1"), "未闭合的对象应解析失败"); + + // 未闭合字符串 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("\"just a string"), "未闭合字符串应解析失败"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":\"unterminated}"), "未闭合字符串应解析失败"); +} + +static void testLoadFailureInvalidTokens(void) +{ + // 非法字符开头 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("x123"), "Parse(非法开头) 应返回 NULL"); + + // 无效数字 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"inter\":16poi}"), "应拒绝无效数字 16poi"); + + // 无效浮点数 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"double\":16.8yu9}"), "应拒绝无效浮点数 16.8yu9"); + + // boolTrue 拼写错误 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"boolTrue\":tru}"), "应拒绝错误拼写的 true (tru)"); + + // boolFalse 拼写错误 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"boolFalse\":fale}"), "应拒绝错误拼写的 false (fale)"); + + // null 拼写错误 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"null\":nul}"), "应拒绝错误拼写的 null (nul)"); + + // null 大写错误(Json 规范要求小写) + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"null\":NULL}"), "应拒绝大写的 NULL"); + + // 缺少逗号 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"inter\":16\"double\":16.89}"), "应拒绝缺少逗号的对象"); + + // 数组项缺少逗号 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("[16,16.89\"hello\"]"), "应拒绝数组项缺少逗号"); + + // 键值缺少引号 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"item:{\"inter\":16}}"), "应拒绝键值缺少引号"); + + // 嵌套对象键值缺少引号 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"item\":{inter:16}}"), "应拒绝嵌套对象键值缺少引号"); + + // 多余引号 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"item\":{\"\"double\":16.89}}"), "应拒绝多余引号"); + + // 键值后多余逗号或引号 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"item\":{\"inter\":16\"\"}}"), "应拒绝非法结尾引号"); + + // 数组中无效数字 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"arrayInt\":[16,16,16m,16,16]}"), "应拒绝数组中含有无效数字 16m"); +} + +static void testLoadFailureInvalidNumbers(void) +{ + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("012"), "Parse(前导 0) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":01}"), "Parse(01) 应返回 NULL (不允许前导 0)"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":-01}"), "Parse(-01) 应返回 NULL (不允许前导 0)"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":00}"), "Parse(00) 应返回 NULL (不允许前导 0)"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":+1}"), "Parse(+1) 应返回 NULL (Json 不允许前导 +)"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":--1}"), "Parse(--1) 应返回 NULL"); + + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1.2.3}"), "Parse(1.2.3) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1.}"), "Parse(1.) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":-.1}"), "Parse(-.1) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":-}"), "Parse(-) 应返回 NULL"); + + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1e}"), "Parse(1e) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1e+}"), "Parse(1e+) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1e-}"), "Parse(1e-) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1e309}"), "Parse(1e309) 应返回 NULL (溢出)"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("1e2147483647"), "Parse(纯数字指数边界) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("1e2147483648"), "Parse(纯数字指数累积溢出) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("1e-2147483648"), "Parse(纯数字负指数累积溢出) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1e2147483648}"), "Parse(指数累积溢出) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1e-2147483648}"), "Parse(负指数累积溢出) 应返回 NULL"); +} + +static void testLoadFailureHugeNumberOverflow(void) +{ + // 超长整数:应在数值累乘过程中触发 isfinite 防御并失败 + const uint32_t intLen = 1024; + char *hugeInt = (char *)malloc((size_t)intLen + 1U); + TEST_ASSERT_NOT_NULL(hugeInt); + + hugeInt[0] = '1'; + memset(hugeInt + 1, '9', intLen - 1U); + hugeInt[intLen] = '\0'; + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse(hugeInt), "Parse(超长整数溢出) 应返回 NULL"); + free(hugeInt); + + // 超长小数:同样应触发 isfinite 防御并失败 + const uint32_t fracLen = 1024; + char *hugeFrac = (char *)malloc((size_t)fracLen + 3U); + TEST_ASSERT_NOT_NULL(hugeFrac); + + hugeFrac[0] = '0'; + hugeFrac[1] = '.'; + memset(hugeFrac + 2, '9', fracLen); + hugeFrac[fracLen + 2U] = '\0'; + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse(hugeFrac), "Parse(超长小数溢出) 应返回 NULL"); + free(hugeFrac); +} + +static void testLoadFailureInvalidEscapes(void) +{ + // 疯狂的转义符 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":\"\\\"}"), "Parse(末尾转义未完成) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":\"\\v\"}"), "Parse(非法转义 \\v) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":\"\\x12\"}"), "Parse(非法转义 \\x12) 应返回 NULL"); + + // 控制字符插入 + // Json 规范不允许未转义的控制字符(0x00-0x1F) + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":\"\x01\"}"), "Parse(含控制字符 0x01) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":\"\n\"}"), "Parse(含未转义换行) 应返回 NULL"); +} + +static void testLoadFailureInvalidUnicode(void) +{ + // Unicode 截断 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"invalid\":\"\\u123\"}"), "应拒绝截断的 Unicode"); + + // 非法十六进制字符 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"invalid\":\"\\uGGGG\"}"), "应拒绝非法 Unicode GGGG"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"invalid\":\"\\u00ZZ\"}"), "应拒绝非法 Unicode 00ZZ"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"invalid\":\"\\u00!!\"}"), "应拒绝非法 Unicode 00!!"); + + // UTF-16 代理对错误 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"invalid\":\"\\uD800\"}"), "应拒绝缺少低位代理的高位代理"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"invalid\":\"\\uDC00\"}"), "应拒绝单独低位代理"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"invalid\":\"\\uD800\\u0041\"}"), "应拒绝无效代理对"); +} + +static void testLoadFailureInvalidStructure(void) +{ + // 结构混乱 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1, \"b\":2, }"), "Parse(尾部逗号) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1, , \"b\":2}"), "Parse(双逗号) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1 : \"b\":2}"), "Parse(冒号代替逗号) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1 \"b\":2}"), "Parse(缺少逗号) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1,}"), "Parse(对象尾逗号) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1,,\"b\":2}"), "Parse(对象双逗号) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":}"), "Parse(缺少值) 应返回 NULL"); +#if true == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"dup\":1,\"dup\":2}"), "严格模式 Parse(对象重复 key) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"obj\":{\"dup\":1,\"dup\":2}}"), "严格模式 Parse(嵌套对象重复 key) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("[{\"dup\":1,\"dup\":2}]"), "严格模式 Parse(数组内对象重复 key) 应返回 NULL"); +#else + RyanJson_t dupObj = RyanJsonParse("{\"dup\":1,\"dup\":2}"); + RyanJson_t dupNestedObj = RyanJsonParse("{\"obj\":{\"dup\":1,\"dup\":2}}"); + RyanJson_t dupInArray = RyanJsonParse("[{\"dup\":1,\"dup\":2}]"); + TEST_ASSERT_NOT_NULL_MESSAGE(dupObj, "非严格模式 Parse(对象重复 key) 应成功"); + TEST_ASSERT_NOT_NULL_MESSAGE(dupNestedObj, "非严格模式 Parse(嵌套对象重复 key) 应成功"); + TEST_ASSERT_NOT_NULL_MESSAGE(dupInArray, "非严格模式 Parse(数组内对象重复 key) 应成功"); + RyanJsonDelete(dupObj); + RyanJsonDelete(dupNestedObj); + RyanJsonDelete(dupInArray); +#endif + + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("[,1]"), "Parse(数组前置逗号) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("[1,,2]"), "Parse(数组双逗号) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("[1,]"), "Parse(数组尾逗号) 应返回 NULL"); +} + +static void testLoadFailureDuplicateKeyAfterDecode(void) +{ +#if true == RyanJsonStrictObjectKeyCheck + // 转义后 key 冲突("\u0061" == "a") + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"a\":1,\"\\u0061\":2}"), "严格模式 Parse(转义后重复 key) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"\\u0061\":1,\"a\":2}"), "严格模式 Parse(转义后重复 key 反序) 应返回 NULL"); + + // 大小写字符同理:"\u0041" == "A" + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"A\":1,\"\\u0041\":2}"), "严格模式 Parse(转义后重复大写 key) 应返回 NULL"); + + // 空 key 重复 + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse("{\"\":1,\"\":2}"), "严格模式 Parse(空 key 重复) 应返回 NULL"); +#else + RyanJson_t dupEscaped1 = RyanJsonParse("{\"a\":1,\"\\u0061\":2}"); + RyanJson_t dupEscaped2 = RyanJsonParse("{\"\\u0061\":1,\"a\":2}"); + RyanJson_t dupEscaped3 = RyanJsonParse("{\"A\":1,\"\\u0041\":2}"); + RyanJson_t dupEscaped4 = RyanJsonParse("{\"\":1,\"\":2}"); + TEST_ASSERT_NOT_NULL_MESSAGE(dupEscaped1, "非严格模式 Parse(转义后重复 key) 应成功"); + TEST_ASSERT_NOT_NULL_MESSAGE(dupEscaped2, "非严格模式 Parse(转义后重复 key 反序) 应成功"); + TEST_ASSERT_NOT_NULL_MESSAGE(dupEscaped3, "非严格模式 Parse(转义后重复大写 key) 应成功"); + TEST_ASSERT_NOT_NULL_MESSAGE(dupEscaped4, "非严格模式 Parse(空 key 重复) 应成功"); + RyanJsonDelete(dupEscaped1); + RyanJsonDelete(dupEscaped2); + RyanJsonDelete(dupEscaped3); + RyanJsonDelete(dupEscaped4); +#endif +} + +static void testLoadFailureMalformedNesting(void) +{ + // 深度嵌套但没有闭合 + char deepOpen[200]; + memset(deepOpen, '[', 199); + deepOpen[199] = '\0'; + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse(deepOpen), "Parse(199个[) 应返回 NULL"); + + // 深度嵌套但不匹配 + char deepMix[200]; + memset(deepMix, '[', 100); + memset(deepMix + 100, '}', 99); // 应该是 ] + deepMix[199] = '\0'; + TEST_ASSERT_NULL_MESSAGE(RyanJsonParse(deepMix), "Parse(100个[ + 99个}) 应返回 NULL"); +} + +static void testLoadParseOptionsFailure(void) +{ + // 禁止尾部垃圾:requireNullTerminator = true + const char *text = " {\"a\":1} trailing"; + RyanJson_t json = RyanJsonParseOptions(text, (uint32_t)strlen(text), RyanJsonTrue, NULL); + TEST_ASSERT_NULL_MESSAGE(json, "ParseOptions(require null terminator) 应失败"); +} + +static int32_t gLoadFailAfter = -1; +static int32_t gLoadAllocCount = 0; + +static void *loadFailMalloc(size_t size) +{ + if (gLoadFailAfter >= 0 && gLoadAllocCount++ >= gLoadFailAfter) { return NULL; } + return unityTestMalloc(size); +} + +static void *loadFailRealloc(void *block, size_t size) +{ + if (gLoadFailAfter >= 0 && gLoadAllocCount++ >= gLoadFailAfter) { return NULL; } + return unityTestRealloc(block, size); +} + +static void loadSetFailAfter(int32_t n) +{ + gLoadFailAfter = n; + gLoadAllocCount = 0; + RyanJsonInitHooks(loadFailMalloc, unityTestFree, loadFailRealloc); +} + +static void loadRestoreHooks(void) +{ + RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc); + gLoadFailAfter = -1; + gLoadAllocCount = 0; +} + +static void testLoadFailureOomParse(void) +{ + const char *longKeyJson = + "{\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\":1}"; + + loadSetFailAfter(0); + RyanJson_t json = RyanJsonParse("{\"a\":1}"); + loadRestoreHooks(); + if (json) { RyanJsonDelete(json); } + TEST_ASSERT_NULL_MESSAGE(json, "Parse OOM(根节点分配失败) 应返回 NULL"); + + loadSetFailAfter(1); // root 成功,key buffer 失败 + json = RyanJsonParse(longKeyJson); + loadRestoreHooks(); + if (json) { RyanJsonDelete(json); } + TEST_ASSERT_NULL_MESSAGE(json, "Parse OOM 应返回 NULL"); +} + +void testLoadFailureRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testLoadFailureNullAndWhitespace); + RUN_TEST(testLoadFailureTruncated); + RUN_TEST(testLoadFailureInvalidTokens); + RUN_TEST(testLoadFailureInvalidNumbers); + RUN_TEST(testLoadFailureHugeNumberOverflow); + RUN_TEST(testLoadFailureInvalidEscapes); + RUN_TEST(testLoadFailureInvalidUnicode); + RUN_TEST(testLoadFailureInvalidStructure); + RUN_TEST(testLoadFailureDuplicateKeyAfterDecode); + RUN_TEST(testLoadFailureMalformedNesting); + RUN_TEST(testLoadParseOptionsFailure); + RUN_TEST(testLoadFailureOomParse); +} diff --git a/test/unityTest/cases/core/testLoadSuccess.c b/test/unityTest/cases/core/testLoadSuccess.c new file mode 100644 index 0000000..ac06178 --- /dev/null +++ b/test/unityTest/cases/core/testLoadSuccess.c @@ -0,0 +1,323 @@ +#include "testBase.h" + +static void testLoadRootZero(void) +{ + RyanJson_t json = RyanJsonParse("0"); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "Parse 数字 \"0\" 作为根节点 应正常"); + if (json) + { + TEST_ASSERT_TRUE(RyanJsonIsInt(json)); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetIntValue(json)); + RyanJsonDelete(json); + } + + json = RyanJsonParse("-0"); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "Parse 数字 \"-0\" 作为根节点 应正常"); + if (json) + { + TEST_ASSERT_TRUE(RyanJsonIsInt(json)); + TEST_ASSERT_EQUAL_INT(0, RyanJsonGetIntValue(json)); + RyanJsonDelete(json); + } +} + +static void testLoadUtf8(void) +{ + // UTF-8 边界测试 + // 双字节字符(© -> \xC2\xA9) + RyanJson_t json = RyanJsonParse("{\"c\":\"\xC2\xA9\"}"); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_EQUAL_STRING("\xC2\xA9", RyanJsonGetStringValue(RyanJsonGetObjectToKey(json, "c"))); + RyanJsonDelete(json); + + // 三字节字符(中 -> \xE4\xB8\xAD) + json = RyanJsonParse("{\"z\":\"\xE4\xB8\xAD\"}"); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_EQUAL_STRING("\xE4\xB8\xAD", RyanJsonGetStringValue(RyanJsonGetObjectToKey(json, "z"))); + RyanJsonDelete(json); + + // 四字节字符(Emoji 🐂 -> \xF0\x9F\x90\x82) + json = RyanJsonParse("{\"e\":\"\xF0\x9F\x90\x82\"}"); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_EQUAL_STRING("\xF0\x9F\x90\x82", RyanJsonGetStringValue(RyanJsonGetObjectToKey(json, "e"))); + RyanJsonDelete(json); +} + +static void testLoadStandardObject(void) +{ + char *str = NULL; + RyanJson_t json; + char *jsonstr = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" + "{\"inter\":16,\"double\":16." + "89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," + "16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," + "\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null}],\"unicode\":\"😀\"}"; + + // 标准对象加载测试 + json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析基础 Json 失败"); + + str = RyanJsonPrint(json, 250, RyanJsonFalse, NULL); + TEST_ASSERT_EQUAL_STRING_MESSAGE(jsonstr, str, "打印生成的字符串与原始字符串不匹配"); + RyanJsonFree(str); + + // 使用公共验证函数进一步检查 + testCheckRoot(json); + RyanJsonDelete(json); +} + +static void testLoadUnicodeValid(void) +{ + char printfBuf[256] = {0}; + char *str = NULL; + RyanJson_t json; + + // Emoji 测试 + json = RyanJsonParse("{\"emoji\":\"\\uD83D\\uDE00\"}"); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Unicode Emoji 失败"); + str = RyanJsonPrintPreallocated(json, printfBuf, sizeof(printfBuf), RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(str, "打印 Unicode Emoji 失败"); + RyanJsonDelete(json); + + // 测试数字 0-9 分支: \u0030 = '0', \u0039 = '9' + json = RyanJsonParse("{\"num\":\"\\u0030\\u0039\"}"); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Unicode 数字失败"); + str = RyanJsonPrintPreallocated(json, printfBuf, sizeof(printfBuf), RyanJsonFalse, NULL); + TEST_ASSERT_EQUAL_STRING_MESSAGE("{\"num\":\"09\"}", str, "Unicode 数字解析/打印错误"); + RyanJsonDelete(json); + + // 测试小写 a-f 分支: \u0061 = 'a', \u0066 = 'f' + json = RyanJsonParse("{\"lower\":\"\\u0061\\u0062\\u0063\\u0064\\u0065\\u0066\"}"); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Unicode 小写字母失败"); + str = RyanJsonPrintPreallocated(json, printfBuf, sizeof(printfBuf), RyanJsonFalse, NULL); + TEST_ASSERT_EQUAL_STRING_MESSAGE("{\"lower\":\"abcdef\"}", str, "Unicode 小写字母解析/打印错误"); + RyanJsonDelete(json); + + // 测试大写 A-F 分支: \u0041 = 'A', \u0046 = 'F' + json = RyanJsonParse("{\"upper\":\"\\u0041\\u0042\\u0043\\u0044\\u0045\\u0046\"}"); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Unicode 大写字母失败"); + str = RyanJsonPrintPreallocated(json, printfBuf, sizeof(printfBuf), RyanJsonFalse, NULL); + TEST_ASSERT_EQUAL_STRING_MESSAGE("{\"upper\":\"ABCDEF\"}", str, "Unicode 大写字母解析/打印错误"); + RyanJsonDelete(json); + + // 测试混合大小写: \uAbCd + json = RyanJsonParse("{\"mixed\":\"\\uAbCd\"}"); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析 Unicode 混合大小写失败"); + RyanJsonFree(RyanJsonPrint(json, 50, RyanJsonFalse, NULL)); + RyanJsonDelete(json); +} + +static void testLoadBoundaryConditionsSuccess(void) +{ + RyanJson_t json; + + // 空结构 + json = RyanJsonParse("{}"); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_TRUE(RyanJsonIsObject(json)); + TEST_ASSERT_NULL(RyanJsonGetObjectValue(json)); + RyanJsonDelete(json); + + json = RyanJsonParse("[]"); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_TRUE(RyanJsonIsArray(json)); + TEST_ASSERT_NULL(RyanJsonGetArrayValue(json)); + RyanJsonDelete(json); + + // 极端空白字符 + const char *wsJson = " \n\t { \r\n \"key\" : [ \t ] \n } \r "; + json = RyanJsonParse(wsJson); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "极端空白字符解析失败"); + TEST_ASSERT_NOT_NULL(RyanJsonGetObjectToKey(json, "key")); + RyanJsonDelete(json); + + // 空 key 和空 strValue + json = RyanJsonParse("{\"\": \"\"}"); + TEST_ASSERT_NOT_NULL(json); + RyanJson_t emptyNode = RyanJsonGetObjectValue(json); + TEST_ASSERT_NOT_NULL(emptyNode); + TEST_ASSERT_EQUAL_STRING("", RyanJsonGetKey(emptyNode)); + TEST_ASSERT_EQUAL_STRING("", RyanJsonGetStringValue(emptyNode)); + RyanJsonDelete(json); + + // 极长 key + char longKeyJson[1024]; + snprintf(longKeyJson, sizeof(longKeyJson), "{\"%s\":1}", + "a_very_long_key_padding_........................................................................"); + json = RyanJsonParse(longKeyJson); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "Parse(长 Key) 失败"); + RyanJsonDelete(json); + + // 纯标量测试 + json = RyanJsonParse("\"just a string\""); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_TRUE(RyanJsonIsString(json)); + TEST_ASSERT_EQUAL_STRING("just a string", RyanJsonGetStringValue(json)); + RyanJsonDelete(json); + + json = RyanJsonParse("123.456"); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_TRUE(RyanJsonIsDouble(json)); + RyanJsonDelete(json); + + // 包含 \0 的输入 (应在 \0 处停止或报错,取决于解析逻辑) + // RyanJsonParse 使用 strlen 确定长度,所以会自动在第一个 \0 处截断 + json = RyanJsonParse("{\"a\":1}\0{\"b\":2}"); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_NULL(RyanJsonGetObjectToKey(json, "b")); + RyanJsonDelete(json); +} + +static void testLoadParseOptionsSuccess(void) +{ + const char *end = NULL; + + // 允许尾部内容:requireNullTerminator = false + const char *text = " {\"a\":1} trailing"; + RyanJson_t json = RyanJsonParseOptions(text, (uint32_t)strlen(text), RyanJsonFalse, &end); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "ParseOptions(允许尾部) 失败"); + TEST_ASSERT_NOT_NULL_MESSAGE(end, "parseEndPtr 不应为 NULL"); + TEST_ASSERT_EQUAL_STRING_MESSAGE(" trailing", end, "parseEndPtr 位置错误"); + RyanJsonDelete(json); + + // 仅包含空白尾部:应成功,parseEndPtr 应指向末尾 + text = "{\"a\":1} \t\r\n"; + json = RyanJsonParseOptions(text, (uint32_t)strlen(text), RyanJsonTrue, &end); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "ParseOptions(空白尾部) 失败"); + TEST_ASSERT_NOT_NULL(end); + TEST_ASSERT_EQUAL_CHAR('\0', *end); + RyanJsonDelete(json); + + // 限长解析:仅解析前半段 + const char *concat = "{\"a\":1}{\"b\":2}"; + uint32_t firstLen = (uint32_t)strlen("{\"a\":1}"); + end = NULL; + json = RyanJsonParseOptions(concat, firstLen, RyanJsonTrue, &end); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "ParseOptions(限长解析) 失败"); + TEST_ASSERT_NOT_NULL(end); + TEST_ASSERT_EQUAL_STRING_MESSAGE("{\"b\":2}", end, "限长解析 parseEndPtr 错误"); + RyanJsonDelete(json); +} + +static void testLoadParseOptionsBinaryTail(void) +{ + // 包含内嵌 '\0' 与后续数据,验证 size 驱动的解析行为 + const char rawText[] = {'{', '\"', 'a', '\"', ':', '1', '}', '\0', '{', '\"', 'b', '\"', ':', '2', '}', '\0'}; + const char *end = NULL; + + RyanJson_t json = RyanJsonParseOptions(rawText, (uint32_t)(sizeof(rawText) - 1U), RyanJsonFalse, &end); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "ParseOptions(含二进制尾部, 允许尾部) 应成功"); + TEST_ASSERT_NOT_NULL(end); + TEST_ASSERT_EQUAL_PTR(rawText + 7, end); + TEST_ASSERT_EQUAL_CHAR('\0', *end); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectToKey(json, "a"))); + RyanJsonDelete(json); + + // requireNullTerminator=true 时,内嵌 '\0' 后仍有剩余数据,应失败 + json = RyanJsonParseOptions(rawText, (uint32_t)(sizeof(rawText) - 1U), RyanJsonTrue, &end); + TEST_ASSERT_NULL_MESSAGE(json, "ParseOptions(含二进制尾部, 强制结尾) 应失败"); +} + +static void testLoadNumberBoundaries(void) +{ + RyanJson_t json = RyanJsonParse("{\"i\":2147483647,\"i2\":-2147483648,\"i3\":2147483648,\"n\":-0}"); + TEST_ASSERT_NOT_NULL(json); + + RyanJson_t i = RyanJsonGetObjectToKey(json, "i"); + RyanJson_t i2 = RyanJsonGetObjectToKey(json, "i2"); + RyanJson_t i3 = RyanJsonGetObjectToKey(json, "i3"); + RyanJson_t n = RyanJsonGetObjectToKey(json, "n"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(i), "2147483647 应解析为 int32_t"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(i2), "-2147483648 应解析为 int32_t"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(i3), "2147483648 应解析为 double"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(n), "-0 应解析为 int32_t"); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, RyanJsonGetIntValue(n), "-0 值错误"); + + RyanJsonDelete(json); + + // 极大负指数会下溢到 0(有限数),应作为合法数字解析成功 + json = RyanJsonParse("1e-2147483647"); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "1e-2147483647 应解析成功"); + if (json) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(json), "1e-2147483647 应解析为 double"); + RyanJsonDelete(json); + } +} + +static void testLoadScientificNumberRoundtrip(void) +{ + const char *jsonText = "{\"a\":1e0,\"b\":1E+2,\"c\":-2.5e-3,\"d\":0e+1}"; + RyanJson_t json = RyanJsonParse(jsonText); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "科学计数法解析失败"); + + RyanJson_t a = RyanJsonGetObjectToKey(json, "a"); + RyanJson_t b = RyanJsonGetObjectToKey(json, "b"); + RyanJson_t c = RyanJsonGetObjectToKey(json, "c"); + RyanJson_t d = RyanJsonGetObjectToKey(json, "d"); + + TEST_ASSERT_TRUE(RyanJsonIsDouble(a)); + TEST_ASSERT_TRUE(RyanJsonIsDouble(b)); + TEST_ASSERT_TRUE(RyanJsonIsDouble(c)); + TEST_ASSERT_TRUE(RyanJsonIsDouble(d)); + + TEST_ASSERT_TRUE(RyanJsonCompareDouble(1.0, RyanJsonGetDoubleValue(a))); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(100.0, RyanJsonGetDoubleValue(b))); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(-0.0025, RyanJsonGetDoubleValue(c))); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(0.0, RyanJsonGetDoubleValue(d))); + + char *printed = RyanJsonPrint(json, 128, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL(printed); + + RyanJson_t roundtrip = RyanJsonParse(printed); + RyanJsonFree(printed); + TEST_ASSERT_NOT_NULL_MESSAGE(roundtrip, "科学计数法往返解析失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(json, roundtrip), "科学计数法往返后 Compare 应相等"); + + RyanJsonDelete(roundtrip); + RyanJsonDelete(json); +} + +static void testLoadDuplicateKeyScopeIsolation(void) +{ + // 允许不同作用域使用同名 key(仅同一 Object 作用域内禁止重复) + const char *jsonText = "{\"a\":1,\"obj\":{\"a\":2},\"arr\":[{\"a\":3},{\"a\":4}]}"; + RyanJson_t json = RyanJsonParse(jsonText); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "不同作用域同名 key 应解析成功"); + + if (json) + { + RyanJson_t arr = RyanJsonGetObjectToKey(json, "arr"); + RyanJson_t arrObj0 = RyanJsonGetObjectByIndex(arr, 0); + RyanJson_t arrObj1 = RyanJsonGetObjectByIndex(arr, 1); + + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectToKey(json, "a"))); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(RyanJsonGetObjectToKey(json, "obj", "a"))); + TEST_ASSERT_EQUAL_INT(3, RyanJsonGetIntValue(RyanJsonGetObjectToKey(arrObj0, "a"))); + TEST_ASSERT_EQUAL_INT(4, RyanJsonGetIntValue(RyanJsonGetObjectToKey(arrObj1, "a"))); + RyanJsonDelete(json); + } +} + +void testLoadSuccessRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testLoadRootZero); + RUN_TEST(testLoadUtf8); + RUN_TEST(testLoadStandardObject); + RUN_TEST(testLoadUnicodeValid); + RUN_TEST(testLoadBoundaryConditionsSuccess); + RUN_TEST(testLoadParseOptionsSuccess); + RUN_TEST(testLoadParseOptionsBinaryTail); + RUN_TEST(testLoadNumberBoundaries); + RUN_TEST(testLoadScientificNumberRoundtrip); + RUN_TEST(testLoadDuplicateKeyScopeIsolation); +} diff --git a/test/unityTest/cases/core/testReplace.c b/test/unityTest/cases/core/testReplace.c new file mode 100644 index 0000000..c59f38c --- /dev/null +++ b/test/unityTest/cases/core/testReplace.c @@ -0,0 +1,487 @@ +#include "testBase.h" + +static void testReplaceEdgeCases(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj, "a", 1); + + // Replace NULL 参数 + RyanJson_t newItem = RyanJsonCreateInt("b", 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByKey(NULL, "key", newItem), "ReplaceByKey(NULL, ...) 应返回 False"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByKey(obj, NULL, newItem), "ReplaceByKey(..., key=NULL, ...) 应返回 False"); + + // 注意:如果 Replace 失败,newItem 及其内存由谁负责? + // 如果 Replace 函数返回 False 且没有接管 item,调用者需要释放 item。 + // RyanJsonReplaceByKey 检查参数失败时直接返回,不触碰 item。 + // 所以这里我们需要手动释放 newItem 以避免内存泄漏。 + RyanJsonDelete(newItem); + + newItem = RyanJsonCreateInt("b", 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByKey(obj, "a", NULL), "ReplaceByKey(..., item=NULL) 应返回 False"); + // newItem 在这里没被传入成功,所以需要手动释放 + RyanJsonDelete(newItem); + + // Replace 不存在的 Key (应失败,而不是添加) + newItem = RyanJsonCreateInt("b", 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByKey(obj, "non_existent", newItem), "ReplaceByKey(不存在的Key) 应返回 False"); + // 同样,替换失败,需释放 newItem + RyanJsonDelete(newItem); + + // Replace Index 越界 + newItem = RyanJsonCreateInt("b", 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByIndex(obj, 99, newItem), "ReplaceByIndex(越界) 应返回 False"); + RyanJsonDelete(newItem); + + // ReplaceByIndex 在对象上的重复 key 行为由严格模式控制 + RyanJsonAddIntToObject(obj, "b", 2); + newItem = RyanJsonCreateInt("a", 9); +#if true == RyanJsonDefaultAddAtHead + uint32_t replaceIndex = 0; // 头插模式下,后加的 "b" 位于索引 0 +#else + uint32_t replaceIndex = 1; // 尾插模式下,后加的 "b" 位于索引 1 +#endif + +#if true == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByIndex(obj, replaceIndex, newItem), + "严格模式下 ReplaceByIndex(Object) 重复 key 应返回 False"); + RyanJsonDelete(newItem); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "b"))); +#else + TEST_ASSERT_TRUE_MESSAGE(RyanJsonReplaceByIndex(obj, replaceIndex, newItem), + "非严格模式下 ReplaceByIndex(Object) 重复 key 应返回 True"); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(9, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); +#else + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); +#endif + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectByKey(obj, "b"), "非严格模式替换后 b 应被替换掉"); +#endif + + // ReplaceByIndex 在对象上使用相同 key 替换应成功 + // 该用例覆盖冲突检查中的 item == skipItem 分支(应跳过被替换节点本身) + newItem = RyanJsonCreateInt("b", 99); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonReplaceByIndex(obj, replaceIndex, newItem), "ReplaceByIndex(Object) 同 key 替换应成功"); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); + TEST_ASSERT_EQUAL_INT(99, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "b"))); + + // 对非容器操作 + RyanJson_t num = RyanJsonCreateInt("num", 1); + newItem = RyanJsonCreateInt("val", 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByKey(num, "any", newItem), "对非容器 ReplaceByKey 应返回 False"); + RyanJsonDelete(newItem); + + RyanJsonDelete(obj); + RyanJsonDelete(num); +} + +static void testReplaceSelfCheck(void) +{ + // 测试:尝试将节点替换为其自身(RyanJsonReplaceByKey/Index 至少应保持稳定,不发生崩溃)。 + // 但 API 语义通常要求 newItem 是新建节点,且不属于任何树(或来自其他位置的 Detach)。 + // 因此这里不直接传入“树内同一节点”,而是使用语义等价的替换场景做覆盖。 + + // 测试:替换为“较重”的新节点(大数组) + RyanJson_t obj = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj, "k", 1); + + RyanJson_t bigArr = RyanJsonCreateArray(); + for (int32_t i = 0; i < 100; i++) + { + RyanJsonAddIntToArray(bigArr, i); + } + + TEST_ASSERT_TRUE(RyanJsonReplaceByKey(obj, "k", bigArr)); + TEST_ASSERT_EQUAL_INT(100, RyanJsonGetArraySize(RyanJsonGetObjectByKey(obj, "k"))); + + RyanJsonDelete(obj); +} + +static void testReplaceRejectAttachedItem(void) +{ + RyanJson_t obj1 = RyanJsonCreateObject(); + RyanJson_t obj2 = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj1, "a", 1); + RyanJsonAddIntToObject(obj2, "b", 2); + + RyanJson_t attachedObjItem = RyanJsonGetObjectByKey(obj1, "a"); + TEST_ASSERT_NOT_NULL(attachedObjItem); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByKey(obj2, "b", attachedObjItem), "已挂树的 item 不应作为 ReplaceByKey 参数"); + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj2, "b"))); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj1, "a"))); + + RyanJson_t arr1 = RyanJsonCreateArray(); + RyanJson_t arr2 = RyanJsonCreateArray(); + RyanJsonAddIntToArray(arr1, 10); + RyanJsonAddIntToArray(arr2, 20); + + RyanJson_t attachedArrItem = RyanJsonGetObjectByIndex(arr1, 0); + TEST_ASSERT_NOT_NULL(attachedArrItem); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonReplaceByIndex(arr2, 0, attachedArrItem), "已挂树的 item 不应作为 ReplaceByIndex 参数"); + TEST_ASSERT_EQUAL_INT(20, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr2, 0))); + TEST_ASSERT_EQUAL_INT(10, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr1, 0))); + + RyanJsonDelete(obj1); + RyanJsonDelete(obj2); + RyanJsonDelete(arr1); + RyanJsonDelete(arr2); +} + +static void testReplaceFailureKeepsItemOwnership(void) +{ + // ReplaceByIndex 失败后,item 应仍由调用方持有 + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "a", 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "b", 2)); + + RyanJson_t conflictItem = RyanJsonCreateInt("a", 9); + TEST_ASSERT_NOT_NULL(conflictItem); +#if true == RyanJsonStrictObjectKeyCheck +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_FALSE(RyanJsonReplaceByIndex(obj, 0, conflictItem)); +#else + TEST_ASSERT_FALSE(RyanJsonReplaceByIndex(obj, 1, conflictItem)); +#endif +#else + TEST_ASSERT_FALSE(RyanJsonReplaceByIndex(obj, 99, conflictItem)); +#endif + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDetachedItem(conflictItem), "ReplaceByIndex 失败后 item 不应被消费"); + + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_TRUE(RyanJsonInsert(arr, 0, conflictItem)); + TEST_ASSERT_EQUAL_INT(9, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 0))); + + // ReplaceByKey 失败(目标 key 不存在)后,item 也应可复用 + RyanJson_t notFoundItem = RyanJsonCreateString("tmp", "v"); + TEST_ASSERT_NOT_NULL(notFoundItem); + TEST_ASSERT_FALSE(RyanJsonReplaceByKey(obj, "not_exist", notFoundItem)); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDetachedItem(notFoundItem), "ReplaceByKey 失败后 item 不应被消费"); + + TEST_ASSERT_TRUE(RyanJsonInsert(arr, UINT32_MAX, notFoundItem)); + TEST_ASSERT_EQUAL_STRING("v", RyanJsonGetStringValue(RyanJsonGetObjectByIndex(arr, 1))); + + RyanJsonDelete(arr); + RyanJsonDelete(obj); +} + +static void testReplaceFailureCallerMustDeleteItem(void) +{ + /* + * 约定验证: + * Replace 失败时库不会释放 item,调用方必须手动释放。 + */ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "a", 1)); + + // ReplaceByKey 失败:目标 key 不存在 + RyanJson_t replaceByKeyItem = RyanJsonCreateString("tmp", "v"); + TEST_ASSERT_NOT_NULL(replaceByKeyItem); + TEST_ASSERT_FALSE(RyanJsonReplaceByKey(obj, "not_exist", replaceByKeyItem)); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDetachedItem(replaceByKeyItem), "ReplaceByKey 失败后 item 应保持游离"); + RyanJsonDelete(replaceByKeyItem); // 调用方主动释放 + + // ReplaceByIndex 失败:索引越界 + RyanJson_t replaceByIndexItem = RyanJsonCreateInt("a", 9); + TEST_ASSERT_NOT_NULL(replaceByIndexItem); + TEST_ASSERT_FALSE(RyanJsonReplaceByIndex(obj, 99, replaceByIndexItem)); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDetachedItem(replaceByIndexItem), "ReplaceByIndex 失败后 item 应保持游离"); + RyanJsonDelete(replaceByIndexItem); // 调用方主动释放 + + // 原树应保持不变 + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "a"))); + + RyanJsonDelete(obj); +} + +static void testReplaceKeyRewriteAndWrapPaths(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "a", 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "b", 2)); + + // 无 key 的容器替换对象字段:应自动包装成 key="a" + RyanJson_t noKeyContainer = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(noKeyContainer); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(noKeyContainer, "x", 7)); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonReplaceByKey(obj, "a", noKeyContainer), "ReplaceByKey(无 key 容器) 应成功"); + + RyanJson_t aNode = RyanJsonGetObjectByKey(obj, "a"); + TEST_ASSERT_NOT_NULL(aNode); + TEST_ASSERT_TRUE(RyanJsonIsObject(aNode)); + TEST_ASSERT_EQUAL_INT(7, RyanJsonGetIntValue(RyanJsonGetObjectByKey(aNode, "x"))); + + // 无 key 的标量替换对象字段:当前实现会包装为 key="a" 的 object,并把标量作为唯一子节点 + RyanJson_t noKeyScalar = RyanJsonCreateInt(NULL, 123); + TEST_ASSERT_NOT_NULL(noKeyScalar); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonReplaceByKey(obj, "a", noKeyScalar), "ReplaceByKey(无 key 标量) 应成功"); + aNode = RyanJsonGetObjectByKey(obj, "a"); + TEST_ASSERT_NOT_NULL(aNode); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsObject(aNode), "无 key 标量替换后应包装为对象节点"); + TEST_ASSERT_EQUAL_UINT32(1, RyanJsonGetSize(aNode)); + TEST_ASSERT_EQUAL_INT(123, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(aNode, 0))); + + // 不同 key 的节点替换:应重命名为目标 key="b" + RyanJson_t diffKeyItem = RyanJsonCreateInt("temp", 88); + TEST_ASSERT_NOT_NULL(diffKeyItem); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonReplaceByKey(obj, "b", diffKeyItem), "ReplaceByKey(不同 key) 应成功"); + TEST_ASSERT_EQUAL_INT(88, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, "b"))); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectByKey(obj, "temp"), "ReplaceByKey 后不应残留旧 key"); + + RyanJsonDelete(obj); +} + +static void testReplaceStandardOperations(void) +{ + + char jsonstr[] = + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"]," + "\"array\":[16,16.89,\"hello\",true,false,null]," + "\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]," + "\"string2222\":\"hello\"}"; + + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "解析基础 Json 失败"); + + // 数组替换测试:arrayInt 头部 + RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 0, RyanJsonCreateString(NULL, "arrayIntHead")); + { + RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayInt"), 0); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 arrayInt[0] 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("arrayIntHead", RyanJsonGetStringValue(v), "替换后的 arrayInt[0] 值错误"); + } + + // 数组替换测试:arrayInt 尾部 + { + RyanJson_t arr = RyanJsonGetObjectToKey(json, "arrayInt"); + uint32_t last = RyanJsonGetSize(arr) - 1; + RyanJsonReplaceByIndex(arr, last, RyanJsonCreateString(NULL, "arrayIntTail")); + RyanJson_t v = RyanJsonGetObjectToIndex(arr, last); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 arrayInt 尾部不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("arrayIntTail", RyanJsonGetStringValue(v), "替换后的 arrayInt 尾部值错误"); + } + + // 数组对象替换测试:arrayItem[0] + RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 0, RyanJsonCreateString(NULL, "arrayItem0")); + { + RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayItem"), 0); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 arrayItem[0] 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("arrayItem0", RyanJsonGetStringValue(v), "替换后的 arrayItem[0] 值错误"); + } + + // 对象字段替换:inter -> 999 + RyanJsonReplaceByKey(json, "inter", RyanJsonCreateInt("inter", 999)); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "inter"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(v), "替换后的 inter 不是整数"); + TEST_ASSERT_EQUAL_INT_MESSAGE(999, RyanJsonGetIntValue(v), "替换后的 inter 值错误"); + } + + // 对象字段替换:double -> 123.45 + RyanJsonReplaceByKey(json, "double", RyanJsonCreateDouble("double", 123.45)); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "double"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(v), "替换后的 double 不是浮点数"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareDouble(123.45, RyanJsonGetDoubleValue(v)), "替换后的 double 值错误"); + } + + // 对象字段替换:string -> "newString" + RyanJsonReplaceByKey(json, "string", RyanJsonCreateString("string", "newString")); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "string"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 string 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("newString", RyanJsonGetStringValue(v), "替换后的 string 值错误"); + } + + // 对象字段替换:boolFalse -> true + RyanJsonReplaceByKey(json, "boolFalse", RyanJsonCreateBool("boolFalse", RyanJsonTrue)); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "boolFalse"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(v), "替换后的 boolFalse 不是布尔值"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonTrue, RyanJsonGetBoolValue(v), "替换后的 boolFalse 值错误"); + } + + // 数组替换:arrayString 中间元素 -> "headString" + RyanJsonReplaceByIndex(RyanJsonGetObjectToKey(json, "arrayString"), 1, RyanJsonCreateString(NULL, "headString")); + { + RyanJson_t v = RyanJsonGetObjectToIndex(RyanJsonGetObjectToKey(json, "arrayString"), 1); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 arrayString[1] 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("headString", RyanJsonGetStringValue(v), "替换后的 arrayString[1] 值错误"); + } + // 数组项替换测试:arrayString 尾部 + { + RyanJson_t arr = RyanJsonGetObjectToKey(json, "arrayString"); + uint32_t last = RyanJsonGetSize(arr) - 1; + RyanJsonReplaceByIndex(arr, last, RyanJsonCreateString(NULL, "tailString")); + RyanJson_t v = RyanJsonGetObjectToIndex(arr, last); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 arrayString 尾部不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("tailString", RyanJsonGetStringValue(v), "替换后的 arrayString 尾部值错误"); + } + + // 数组对象替换:arrayItem 尾部 -> "arrayItemTail" + { + RyanJson_t arr = RyanJsonGetObjectToKey(json, "arrayItem"); + uint32_t last = RyanJsonGetSize(arr) - 1; + RyanJsonReplaceByIndex(arr, last, RyanJsonCreateString(NULL, "arrayItemTail")); + RyanJson_t v = RyanJsonGetObjectToIndex(arr, last); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 arrayItem 尾部不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("arrayItemTail", RyanJsonGetStringValue(v), "替换后的 arrayItem 尾部值错误"); + } + + // 嵌套对象替换:item.inter -> 111 + RyanJsonReplaceByKey(RyanJsonGetObjectToKey(json, "item"), "inter", RyanJsonCreateInt("inter", 111)); + { + RyanJson_t v = RyanJsonGetObjectToKey(RyanJsonGetObjectToKey(json, "item"), "inter"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(v), "替换后的 item.inter 不是整数"); + TEST_ASSERT_EQUAL_INT_MESSAGE(111, RyanJsonGetIntValue(v), "替换后的 item.inter 值错误"); + } + + // 嵌套对象替换:item.string -> "nestedReplace" + RyanJsonReplaceByKey(RyanJsonGetObjectToKey(json, "item"), "string", RyanJsonCreateString("string", "nestedReplace")); + { + RyanJson_t v = RyanJsonGetObjectToKey(RyanJsonGetObjectToKey(json, "item"), "string"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 item.string 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("nestedReplace", RyanJsonGetStringValue(v), "替换后的 item.string 值错误"); + } + + // 混合数组替换测试 + RyanJson_t mixArr = RyanJsonGetObjectToKey(json, "array"); + + // int32_t -> "intReplaced" + RyanJsonReplaceByIndex(mixArr, 0, RyanJsonCreateString(NULL, "intReplaced")); + { + RyanJson_t v = RyanJsonGetObjectToIndex(mixArr, 0); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 array[0] 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("intReplaced", RyanJsonGetStringValue(v), "替换后的 array[0] 值错误"); + } + + // double -> "doubleReplaced" + RyanJsonReplaceByIndex(mixArr, 1, RyanJsonCreateString(NULL, "doubleReplaced")); + { + RyanJson_t v = RyanJsonGetObjectToIndex(mixArr, 1); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 array[1] 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("doubleReplaced", RyanJsonGetStringValue(v), "替换后的 array[1] 值错误"); + } + // string -> "stringReplaced" + RyanJsonReplaceByIndex(mixArr, 2, RyanJsonCreateString(NULL, "stringReplaced")); + { + RyanJson_t v = RyanJsonGetObjectToIndex(mixArr, 2); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 array[2] 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("stringReplaced", RyanJsonGetStringValue(v), "替换后的 array[2] 值错误"); + } + // bool -> "boolReplaced" + RyanJsonReplaceByIndex(mixArr, 3, RyanJsonCreateString(NULL, "boolReplaced")); + { + RyanJson_t v = RyanJsonGetObjectToIndex(mixArr, 3); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 array[3] 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("boolReplaced", RyanJsonGetStringValue(v), "替换后的 array[3] 值错误"); + } + + // null -> "nullReplaced" + RyanJsonReplaceByIndex(mixArr, 5, RyanJsonCreateString(NULL, "nullReplaced")); + { + RyanJson_t v = RyanJsonGetObjectToIndex(mixArr, 5); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 array[5] 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("nullReplaced", RyanJsonGetStringValue(v), "替换后的 array[5] 值错误"); + } + + // 替换整个数组项:arrayString -> "arrayStringRenamed" + RyanJsonReplaceByKey(json, "arrayString", RyanJsonCreateString("arrayString", "arrayStringRenamed")); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "arrayString"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 arrayString 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("arrayStringRenamed", RyanJsonGetStringValue(v), "替换后的 arrayString 值错误"); + } + + // 修改数组节点为对象节点:arrayDouble -> duplicate(item) + RyanJson_t duplicateJson = RyanJsonDuplicate(RyanJsonGetObjectToKey(json, "item")); + RyanJsonReplaceByKey(json, "arrayDouble", duplicateJson); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "arrayDouble"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsObject(v), "替换后的 arrayDouble 不是对象类型"); + } + + // 替换字符串字段:string2222 -> "world" + RyanJsonReplaceByKey(json, "string2222", RyanJsonCreateString("string2222", "world")); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "string2222"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 string2222 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("world", RyanJsonGetStringValue(v), "替换后的 string2222 值错误"); + } + + // 替换 boolValue:boolTrue -> false + RyanJsonReplaceByKey(json, "boolTrue", RyanJsonCreateBool("boolTrue", RyanJsonFalse)); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "boolTrue"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(v), "替换后的 boolTrue 不是布尔值"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonFalse, RyanJsonGetBoolValue(v), "替换后的 boolTrue 值错误"); + } + + // 替换 null 为字符串 + RyanJsonReplaceByKey(json, "null", RyanJsonCreateString("null", "notNull")); + { + RyanJson_t v = RyanJsonGetObjectToKey(json, "null"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(v), "替换后的 null 不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("notNull", RyanJsonGetStringValue(v), "替换后的 null 值错误"); + } + + char *str = RyanJsonPrint(json, 1024, RyanJsonTrue, NULL); + RyanJsonFree(str); + RyanJsonDelete(json); +} + +static void testReplaceTypeSwitchingStress(void) +{ + RyanJson_t root = RyanJsonCreateObject(); + RyanJsonAddIntToObject(root, "k", 1); + + // 疯狂类型切换 + // 高频类型切换 + // Int -> String + TEST_ASSERT_TRUE(RyanJsonReplaceByKey(root, "k", RyanJsonCreateString("k", "s"))); + TEST_ASSERT_TRUE(RyanJsonIsString(RyanJsonGetObjectByKey(root, "k"))); + + // String -> Array + TEST_ASSERT_TRUE(RyanJsonReplaceByKey(root, "k", RyanJsonCreateArray())); + TEST_ASSERT_TRUE(RyanJsonIsArray(RyanJsonGetObjectByKey(root, "k"))); + + // 数组节点替换为对象节点 + TEST_ASSERT_TRUE(RyanJsonReplaceByKey(root, "k", RyanJsonCreateObject())); + TEST_ASSERT_TRUE(RyanJsonIsObject(RyanJsonGetObjectByKey(root, "k"))); + + // 对象节点替换为布尔节点 + TEST_ASSERT_TRUE(RyanJsonReplaceByKey(root, "k", RyanJsonCreateBool("k", RyanJsonTrue))); + TEST_ASSERT_TRUE(RyanJsonIsBool(RyanJsonGetObjectByKey(root, "k"))); + + // Bool -> Null + TEST_ASSERT_TRUE(RyanJsonReplaceByKey(root, "k", RyanJsonCreateNull("k"))); + TEST_ASSERT_TRUE(RyanJsonIsNull(RyanJsonGetObjectByKey(root, "k"))); + + // 自身替换(模拟):实际替换为副本节点 + RyanJson_t nullNode = RyanJsonGetObjectByKey(root, "k"); + RyanJson_t dupNull = RyanJsonDuplicate(nullNode); + TEST_ASSERT_TRUE(RyanJsonReplaceByKey(root, "k", dupNull)); + TEST_ASSERT_TRUE(RyanJsonIsNull(RyanJsonGetObjectByKey(root, "k"))); + + RyanJsonDelete(root); +} + +void testReplaceRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testReplaceEdgeCases); + RUN_TEST(testReplaceSelfCheck); + RUN_TEST(testReplaceRejectAttachedItem); + RUN_TEST(testReplaceFailureKeepsItemOwnership); + RUN_TEST(testReplaceFailureCallerMustDeleteItem); + RUN_TEST(testReplaceKeyRewriteAndWrapPaths); + RUN_TEST(testReplaceStandardOperations); + RUN_TEST(testReplaceTypeSwitchingStress); +} diff --git a/test/unityTest/cases/equality/testEqualityBool.c b/test/unityTest/cases/equality/testEqualityBool.c new file mode 100644 index 0000000..6ddd445 --- /dev/null +++ b/test/unityTest/cases/equality/testEqualityBool.c @@ -0,0 +1,128 @@ +#include "testBase.h" + +void testEqualityBoolEdgeCases(void) +{ + // NULL 输入 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsBool(NULL), "RyanJsonIsBool(NULL) 应返回 false"); + + // 类型混淆测试 + RyanJson_t num = RyanJsonCreateInt("num", 123); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsBool(num), "RyanJsonIsBool(Int) 应返回 false"); + RyanJsonDelete(num); + + RyanJson_t str = RyanJsonCreateString("str", "true"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsBool(str), "RyanJsonIsBool(String) 应返回 false"); + RyanJsonDelete(str); + + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsBool(obj), "RyanJsonIsBool(Object) 应返回 false"); + RyanJsonDelete(obj); + + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsBool(arr), "RyanJsonIsBool(Array) 应返回 false"); + RyanJsonDelete(arr); + + RyanJson_t nullNode = RyanJsonCreateNull("null"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsBool(nullNode), "RyanJsonIsBool(Null) 应返回 false"); + RyanJsonDelete(nullNode); +} + +/** + * @brief boolValue 基础一致性测试 + */ +static void testEqualityBoolBasic(void) +{ + // 测试 true + { + const char *jsonBoolStr = "{\"bool\":true}"; + RyanJson_t jsonRoot = RyanJsonParse(jsonBoolStr); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, "解析包含 true 的 Json 失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectToKey(jsonRoot, "bool")), "字段 'bool' 不是布尔类型"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonTrue, RyanJsonGetBoolValue(RyanJsonGetObjectToKey(jsonRoot, "bool")), + "字段 'bool' 的值不是 true"); + + // 往返校验(序列化 -> 解析 -> 再验证) + char *serializedStr = RyanJsonPrint(jsonRoot, 64, RyanJsonFalse, NULL); + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + TEST_ASSERT_NOT_NULL_MESSAGE(roundtripJson, "往返测试:重新解析失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectToKey(roundtripJson, "bool")), "往返测试:字段 'bool' 类型错误"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonTrue, RyanJsonGetBoolValue(RyanJsonGetObjectToKey(roundtripJson, "bool")), + "往返测试:字段 'bool' 的值错误"); + + RyanJsonDelete(roundtripJson); + } + + // 测试 false + { + const char *jsonBoolStr = "{\"bool\":false}"; + RyanJson_t jsonRoot = RyanJsonParse(jsonBoolStr); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, "解析包含 false 的 Json 失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectToKey(jsonRoot, "bool")), "字段 'bool' 不是布尔类型"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonFalse, RyanJsonGetBoolValue(RyanJsonGetObjectToKey(jsonRoot, "bool")), + "字段 'bool' 的值不是 false"); + + // 往返测试 + char *serializedStr = RyanJsonPrint(jsonRoot, 64, RyanJsonFalse, NULL); + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + TEST_ASSERT_NOT_NULL_MESSAGE(roundtripJson, "往返测试:重新解析失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectToKey(roundtripJson, "bool")), "往返测试:字段 'bool' 类型错误"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonFalse, RyanJsonGetBoolValue(RyanJsonGetObjectToKey(roundtripJson, "bool")), + "往返测试:字段 'bool' 的值错误"); + + RyanJsonDelete(roundtripJson); + } +} + +/** + * @brief 布尔数组一致性测试 + */ +static void testEqualityBoolArray(void) +{ + const char *jsonArrayStr = "[true, false, true, false]"; + RyanJson_t jsonRoot = RyanJsonParse(jsonArrayStr); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, "解析布尔数组失败"); + TEST_ASSERT_EQUAL_INT_MESSAGE(4, RyanJsonGetArraySize(jsonRoot), "数组大小不正确"); + + RyanJsonBool_e expected[] = {RyanJsonTrue, RyanJsonFalse, RyanJsonTrue, RyanJsonFalse}; + int32_t idx = 0; + RyanJson_t item = NULL; + RyanJsonArrayForEach(jsonRoot, item) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(item), "数组元素不是布尔类型"); + TEST_ASSERT_EQUAL_INT_MESSAGE(expected[idx], RyanJsonGetBoolValue(item), "数组元素值不匹配"); + idx++; + } + + // 往返测试 + char *serializedStr = RyanJsonPrint(jsonRoot, 64, RyanJsonFalse, NULL); + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + TEST_ASSERT_NOT_NULL_MESSAGE(roundtripJson, "往返测试:重新解析数组失败"); + TEST_ASSERT_EQUAL_INT_MESSAGE(4, RyanJsonGetArraySize(roundtripJson), "往返测试:数组大小不正确"); + + idx = 0; + RyanJsonArrayForEach(roundtripJson, item) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(item), "往返测试:数组元素不是布尔类型"); + TEST_ASSERT_EQUAL_INT_MESSAGE(expected[idx], RyanJsonGetBoolValue(item), "往返测试:数组元素值不匹配"); + idx++; + } + + RyanJsonDelete(roundtripJson); +} + +void testEqualityBoolRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testEqualityBoolEdgeCases); + RUN_TEST(testEqualityBoolBasic); + RUN_TEST(testEqualityBoolArray); +} diff --git a/test/unityTest/cases/equality/testEqualityDouble.c b/test/unityTest/cases/equality/testEqualityDouble.c new file mode 100644 index 0000000..7710c4a --- /dev/null +++ b/test/unityTest/cases/equality/testEqualityDouble.c @@ -0,0 +1,367 @@ +#include "testBase.h" + +#define DoubleList \ + /* 零值测试 */ \ + X(0.0) \ + X(-0.0) \ + /* 正负整数边界 */ \ + X(1.0) \ + X(-1.0) \ + X(2.0) \ + X(-2.0) \ + X(10.0) \ + X(-10.0) \ + X(100.0) \ + X(1000.0) \ + X(10000.0) \ + X(100000.0) \ + /* 简单小数(二进制精确表示) */ \ + X(0.5) \ + X(-0.5) \ + X(0.25) \ + X(-0.25) \ + X(0.125) \ + X(0.0625) \ + X(0.03125) \ + X(0.015625) \ + /* 常见小数 */ \ + X(16.89) \ + X(-16.89) \ + X(123.456) \ + X(-123.456) \ + X(99.99) \ + X(-99.99) \ + X(1.5) \ + X(2.5) \ + X(3.5) \ + /* 小于1的小数 */ \ + X(0.001) \ + X(-0.001) \ + X(0.0001) \ + X(0.00001) \ + X(0.000001) \ + X(0.123456789) \ + X(0.987654321) \ + X(0.111111111111111) \ + /* 大数测试 */ \ + X(999999.999999) \ + X(-999999.999999) \ + X(12345678.9) \ + X(99999999.0) \ + X(123456789.123456) \ + X(9876543210.12345) \ + /* 科学计数法 - 大数 */ \ + X(1.5e10) \ + X(-1.5e10) \ + X(1.23e8) \ + X(9.99e12) \ + X(1.0e15) \ + X(1.0e18) \ + X(1.0e20) \ + X(5.55e15) \ + /* 科学计数法 - 小数 */ \ + X(1.5e-10) \ + X(-1.5e-10) \ + X(9.87e-5) \ + X(1.0e-15) \ + X(5.5e-8) \ + X(1.0e-18) \ + X(1.0e-20) \ + X(1.23e-3) \ + X(-9.87e-7) \ + /* 数学常量 */ \ + X(3.14159265358979) \ + X(2.71828182845904) \ + X(1.41421356237309) \ + X(1.73205080756888) \ + X(1.61803398874989) \ + X(0.69314718055994) \ + /* 浮点精度经典测试 */ \ + X(0.1) \ + X(0.2) \ + X(0.3) \ + X(0.6) \ + X(0.7) \ + X(0.9) \ + X(0.123456) \ + /* 整数边界值 */ \ + X(2147483647.0) \ + X(-2147483648.0) \ + X(4294967295.0) \ + X(9007199254740991.0) \ + X(-9007199254740991.0) \ + /* 极端小值 */ \ + X(1.0e-100) \ + X(-1.0e-100) \ + X(1.0e-200) \ + X(1.0e-300) \ + X(2.225073858507201e-308) \ + /* 极端大值 */ \ + X(1.0e100) \ + X(-1.0e100) \ + X(1.0e200) \ + X(1.0e300) \ + X(1.797693134862315e308) \ + /* 特殊精度值 */ \ + X(1.0000000000001) \ + X(0.9999999999999) \ + X(1.23456789012345) \ + X(9.87654321098765) \ + /* 重复数字模式 */ \ + X(1.1111111111111) \ + X(2.2222222222222) \ + X(9.9999999999999) \ + /* 混合符号和指数 */ \ + X(-1.23e-45) \ + X(-9.87e67) \ + X(1.11e-11) \ + X(-2.22e22) + +static const double DoubleValueTable[] = { +#define X(a) a, + DoubleList +#undef X +}; + +static const char *DoubleStringTable[] = { +#define X(a) "{\"double\":" #a "}", + DoubleList +#undef X +}; + +static const char *DoubleStringTable2[] = { +#define X(a) #a, + DoubleList +#undef X +}; + +static const char *DoubleStringTable3[] = { +#define X(a) "[" #a "]", + DoubleList +#undef X +}; + +static RyanJsonBool_e shouldSkipLargeDoubleWhenNoScientific(double value) +{ +#if false == RyanJsonSnprintfSupportScientific + /* 不支持科学计数法的平台上,跳过超大值序列化往返校验。 */ + return RyanJsonMakeBool(fabs(value) >= 1.0e100); +#else + (void)value; + return RyanJsonFalse; +#endif +} + +/** + * @brief 浮点类型边界与一致性测试 + */ +void testEqualityDoubleEdgeCases(void) +{ + // NULL 输入 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsDouble(NULL), "RyanJsonIsDouble(NULL) 应返回 false"); + + // 类型混淆测试 + RyanJson_t num = RyanJsonCreateInt("num", 123); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsNumber(num), "RyanJsonIsNumber(Int) 应返回 true"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsDouble(num), "RyanJsonIsDouble(Int) 应返回 false"); + RyanJsonDelete(num); + + RyanJson_t str = RyanJsonCreateString("str", "123.456"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsDouble(str), "RyanJsonIsDouble(String) 应返回 false"); + RyanJsonDelete(str); + + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsDouble(obj), "RyanJsonIsDouble(Object) 应返回 false"); + RyanJsonDelete(obj); + + RyanJson_t boolNode = RyanJsonCreateBool("bool", RyanJsonTrue); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsDouble(boolNode), "RyanJsonIsDouble(Bool) 应返回 false"); + RyanJsonDelete(boolNode); +} + +static void testEqualityDoubleTypeIdentity(void) +{ + // 指数形式即使数值是整数,也应保持 double 类型 + RyanJson_t expNode = RyanJsonParse("1e0"); + TEST_ASSERT_NOT_NULL(expNode); + TEST_ASSERT_TRUE(RyanJsonIsDouble(expNode)); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(1.0, RyanJsonGetDoubleValue(expNode))); + + char *expPrinted = RyanJsonPrint(expNode, 64, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL(expPrinted); + RyanJson_t expRoundtrip = RyanJsonParse(expPrinted); + RyanJsonFree(expPrinted); + TEST_ASSERT_NOT_NULL(expRoundtrip); + TEST_ASSERT_TRUE(RyanJsonIsDouble(expRoundtrip)); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(1.0, RyanJsonGetDoubleValue(expRoundtrip))); + RyanJsonDelete(expRoundtrip); + RyanJsonDelete(expNode); + + // 小数形式应保持 double + RyanJson_t fracNode = RyanJsonParse("-2.5000"); + TEST_ASSERT_NOT_NULL(fracNode); + TEST_ASSERT_TRUE(RyanJsonIsDouble(fracNode)); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(-2.5, RyanJsonGetDoubleValue(fracNode))); + RyanJsonDelete(fracNode); + + // 超出 int32_t 范围的纯数字,应归类为 double + RyanJson_t overInt = RyanJsonParse("2147483648"); + TEST_ASSERT_NOT_NULL(overInt); + TEST_ASSERT_TRUE(RyanJsonIsDouble(overInt)); + TEST_ASSERT_FALSE(RyanJsonIsInt(overInt)); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(2147483648.0, RyanJsonGetDoubleValue(overInt))); + RyanJsonDelete(overInt); + + RyanJson_t belowInt = RyanJsonParse("-2147483649"); + TEST_ASSERT_NOT_NULL(belowInt); + TEST_ASSERT_TRUE(RyanJsonIsDouble(belowInt)); + TEST_ASSERT_FALSE(RyanJsonIsInt(belowInt)); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(-2147483649.0, RyanJsonGetDoubleValue(belowInt))); + RyanJsonDelete(belowInt); +} + +static void testEqualityDoubleTableCommon(const char *const *stringTable, RyanJsonBool_e withKey) +{ + for (uint32_t i = 0; i < sizeof(DoubleValueTable) / sizeof(DoubleValueTable[0]); i++) + { + double expectValue = DoubleValueTable[i]; + if (shouldSkipLargeDoubleWhenNoScientific(expectValue)) { continue; } + + const char *jsondoubleStr = stringTable[i]; + RyanJson_t jsonRoot = RyanJsonParse(jsondoubleStr); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, jsondoubleStr); + + RyanJson_t valueNode = withKey ? RyanJsonGetObjectToKey(jsonRoot, "double") : jsonRoot; + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(valueNode), withKey ? "字段 'double' 类型错误" : "Root node not double"); + + // 验证解析后的数值是否正确 + double doubleValue = RyanJsonGetDoubleValue(valueNode); + if (!RyanJsonCompareDouble(expectValue, doubleValue)) + { + TEST_PRINTF("字符串: %s, 期望: %g, 实际: %g", jsondoubleStr, expectValue, doubleValue); + TEST_FAIL(); + } + + // 往返校验:序列化后再次解析,数值应保持一致 + char *serializedStr = RyanJsonPrint(jsonRoot, 128, RyanJsonFalse, NULL); + if (NULL == serializedStr) + { + TEST_PRINTF("序列化失败样本: %s", jsondoubleStr); + RyanJsonDelete(jsonRoot); + TEST_FAIL_MESSAGE("序列化失败"); + } + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + if (NULL == roundtripJson) + { + TEST_PRINTF("往返解析失败样本: %s", jsondoubleStr); + TEST_FAIL_MESSAGE("往返测试:重新解析失败"); + } + RyanJson_t roundtripValueNode = withKey ? RyanJsonGetObjectToKey(roundtripJson, "double") : roundtripJson; + if (RyanJsonFalse == RyanJsonIsDouble(roundtripValueNode)) + { + RyanJsonDelete(roundtripJson); + TEST_FAIL_MESSAGE(withKey ? "往返测试:字段 'double' 类型错误" : "往返测试:Root node not double"); + } + + double roundtripValue = RyanJsonGetDoubleValue(roundtripValueNode); + if (!RyanJsonCompareDouble(expectValue, roundtripValue)) + { + TEST_PRINTF("往返测试失败:期望: %g, 实际: %g", expectValue, roundtripValue); + TEST_FAIL(); + } + + RyanJsonDelete(roundtripJson); + } +} + +static void testEqualityDoubleTable(void) +{ + testEqualityDoubleTableCommon(DoubleStringTable, RyanJsonTrue); +} + +static void testEqualityDoubleTable2(void) +{ + testEqualityDoubleTableCommon(DoubleStringTable2, RyanJsonFalse); +} + +static void testEqualityDoubleTable3(void) +{ + for (uint32_t i = 0; i < sizeof(DoubleValueTable) / sizeof(DoubleValueTable[0]); i++) + { + double expectValue = DoubleValueTable[i]; + if (shouldSkipLargeDoubleWhenNoScientific(expectValue)) { continue; } + + const char *jsondoubleStr = DoubleStringTable3[i]; + RyanJson_t jsonRoot = RyanJsonParse(jsondoubleStr); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, jsondoubleStr); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsArray(jsonRoot), "Root node not array"); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, RyanJsonGetSize(jsonRoot), "Array size not 1"); + + RyanJson_t valueNode = RyanJsonGetObjectByIndex(jsonRoot, 0); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(valueNode), "Array[0] not double"); + + double doubleValue = RyanJsonGetDoubleValue(valueNode); + if (!RyanJsonCompareDouble(expectValue, doubleValue)) + { + TEST_PRINTF("字符串: %s, 期望: %g, 实际: %g", jsondoubleStr, expectValue, doubleValue); + TEST_FAIL(); + } + + // 往返校验:序列化后再次解析,数值应保持一致 + char *serializedStr = RyanJsonPrint(jsonRoot, 128, RyanJsonFalse, NULL); + if (NULL == serializedStr) + { + TEST_PRINTF("序列化失败样本: %s", jsondoubleStr); + RyanJsonDelete(jsonRoot); + TEST_FAIL_MESSAGE("序列化失败"); + } + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + if (NULL == roundtripJson) + { + TEST_PRINTF("往返解析失败样本: %s", jsondoubleStr); + TEST_FAIL_MESSAGE("往返测试:重新解析失败"); + } + if (RyanJsonFalse == RyanJsonIsArray(roundtripJson)) + { + RyanJsonDelete(roundtripJson); + TEST_FAIL_MESSAGE("往返测试:Root node not array"); + } + if (1 != RyanJsonGetSize(roundtripJson)) + { + RyanJsonDelete(roundtripJson); + TEST_FAIL_MESSAGE("往返测试:Array size not 1"); + } + + RyanJson_t roundtripValueNode = RyanJsonGetObjectByIndex(roundtripJson, 0); + if (RyanJsonFalse == RyanJsonIsDouble(roundtripValueNode)) + { + RyanJsonDelete(roundtripJson); + TEST_FAIL_MESSAGE("往返测试:Array[0] not double"); + } + + double roundtripValue = RyanJsonGetDoubleValue(roundtripValueNode); + if (!RyanJsonCompareDouble(expectValue, roundtripValue)) + { + TEST_PRINTF("往返测试失败:期望: %g, 实际: %g", expectValue, roundtripValue); + TEST_FAIL(); + } + + RyanJsonDelete(roundtripJson); + } +} + +void testEqualityDoubleRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testEqualityDoubleEdgeCases); + RUN_TEST(testEqualityDoubleTypeIdentity); + RUN_TEST(testEqualityDoubleTable); + RUN_TEST(testEqualityDoubleTable2); + RUN_TEST(testEqualityDoubleTable3); +} diff --git a/test/unityTest/cases/equality/testEqualityInt.c b/test/unityTest/cases/equality/testEqualityInt.c new file mode 100644 index 0000000..c91f86e --- /dev/null +++ b/test/unityTest/cases/equality/testEqualityInt.c @@ -0,0 +1,286 @@ +#include "testBase.h" + +#define IntList \ + /* 零值测试 */ \ + X(0) \ + X(-0) \ + /* 正负边界 */ \ + X(1) \ + X(-1) \ + X(2) \ + X(-2) \ + /* 常见小整数 */ \ + X(10) \ + X(-10) \ + X(100) \ + X(-100) \ + X(255) \ + X(-255) \ + X(256) \ + X(-256) \ + /* 常见数值 */ \ + X(1000) \ + X(-1000) \ + X(9999) \ + X(-9999) \ + X(12345) \ + X(-12345) \ + X(65535) \ + X(-65535) \ + X(65536) \ + X(-65536) \ + /* 大整数 */ \ + X(100000) \ + X(-100000) \ + X(1000000) \ + X(-1000000) \ + X(10000000) \ + X(-10000000) \ + X(100000000) \ + X(-100000000) \ + X(1000000000) \ + X(-1000000000) \ + /* 8位边界 */ \ + X(127) \ + X(-128) \ + /* 16位边界 */ \ + X(32767) \ + X(-32768) \ + /* 32位边界 */ \ + X(2147483647) \ + X(-2147483648) \ + /* 特殊模式 */ \ + X(1234567890) \ + X(-1234567890) \ + X(123456789) \ + X(-123456789) \ + /* 2的幂次 */ \ + X(2) \ + X(4) \ + X(8) \ + X(16) \ + X(32) \ + X(64) \ + X(128) \ + X(512) \ + X(1024) \ + X(2048) \ + X(4096) \ + X(8192) \ + X(16384) \ + X(32768) \ + X(65536) \ + X(131072) \ + X(262144) \ + X(524288) \ + X(1048576) \ + X(2097152) \ + X(4194304) \ + X(8388608) \ + X(16777216) \ + X(33554432) \ + X(67108864) \ + X(134217728) \ + X(268435456) \ + X(536870912) \ + X(1073741824) + +static const int32_t IntValueTable[] = { +#define X(a) a, + IntList +#undef X +}; + +static const char *IntStringTable[] = { +#define X(a) "{\"int32_t\":" #a "}", + IntList +#undef X +}; + +static const char *IntStringTable2[] = { +#define X(a) #a, + IntList +#undef X +}; + +static const char *IntStringTable3[] = { +#define X(a) "[" #a "]", + IntList +#undef X +}; + +/** + * @brief 整数类型边界与一致性测试 + */ +void testEqualityIntEdgeCases(void) +{ + // NULL 输入 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsInt(NULL), "RyanJsonIsInt(NULL) 应返回 false"); + + // 类型混淆测试 + RyanJson_t dbl = RyanJsonCreateDouble("dbl", 123.456); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsNumber(dbl), "RyanJsonIsNumber(Double) 应返回 true"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsInt(dbl), "RyanJsonIsInt(Double) 应返回 false"); + RyanJsonDelete(dbl); + + RyanJson_t str = RyanJsonCreateString("str", "123"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsInt(str), "RyanJsonIsInt(String) 应返回 false"); + RyanJsonDelete(str); + + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsInt(obj), "RyanJsonIsInt(Object) 应返回 false"); + RyanJsonDelete(obj); + + RyanJson_t boolNode = RyanJsonCreateBool("bool", RyanJsonTrue); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsInt(boolNode), "RyanJsonIsInt(Bool) 应返回 false"); + RyanJsonDelete(boolNode); +} + +static void testEqualityIntTypeBoundaries(void) +{ + // int32_t 边界内:应为 int + RyanJson_t intMax = RyanJsonParse("2147483647"); + TEST_ASSERT_NOT_NULL(intMax); + TEST_ASSERT_TRUE(RyanJsonIsInt(intMax)); + TEST_ASSERT_EQUAL_INT32(2147483647, RyanJsonGetIntValue(intMax)); + RyanJsonDelete(intMax); + + RyanJson_t intMin = RyanJsonParse("-2147483648"); + TEST_ASSERT_NOT_NULL(intMin); + TEST_ASSERT_TRUE(RyanJsonIsInt(intMin)); + TEST_ASSERT_EQUAL_INT32(INT32_MIN, RyanJsonGetIntValue(intMin)); + RyanJsonDelete(intMin); + + // 超出 int32_t 范围:应退化为 double + RyanJson_t overMax = RyanJsonParse("2147483648"); + TEST_ASSERT_NOT_NULL(overMax); + TEST_ASSERT_TRUE(RyanJsonIsDouble(overMax)); + TEST_ASSERT_FALSE(RyanJsonIsInt(overMax)); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(2147483648.0, RyanJsonGetDoubleValue(overMax))); + RyanJsonDelete(overMax); + + RyanJson_t belowMin = RyanJsonParse("-2147483649"); + TEST_ASSERT_NOT_NULL(belowMin); + TEST_ASSERT_TRUE(RyanJsonIsDouble(belowMin)); + TEST_ASSERT_FALSE(RyanJsonIsInt(belowMin)); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(-2147483649.0, RyanJsonGetDoubleValue(belowMin))); + RyanJsonDelete(belowMin); + + // 指数/小数语义:数值等于整数也应按 double 处理 + RyanJson_t expInt = RyanJsonParse("1e0"); + TEST_ASSERT_NOT_NULL(expInt); + TEST_ASSERT_TRUE(RyanJsonIsDouble(expInt)); + TEST_ASSERT_FALSE(RyanJsonIsInt(expInt)); + RyanJsonDelete(expInt); + + RyanJson_t fracInt = RyanJsonParse("1.0"); + TEST_ASSERT_NOT_NULL(fracInt); + TEST_ASSERT_TRUE(RyanJsonIsDouble(fracInt)); + TEST_ASSERT_FALSE(RyanJsonIsInt(fracInt)); + RyanJsonDelete(fracInt); + + // -0 保持 int 路径 + RyanJson_t negZero = RyanJsonParse("-0"); + TEST_ASSERT_NOT_NULL(negZero); + TEST_ASSERT_TRUE(RyanJsonIsInt(negZero)); + TEST_ASSERT_EQUAL_INT32(0, RyanJsonGetIntValue(negZero)); + RyanJsonDelete(negZero); +} + +static void testEqualityIntTableCommon(const char *const *stringTable, RyanJsonBool_e withKey) +{ + for (uint32_t i = 0; i < sizeof(IntValueTable) / sizeof(IntValueTable[0]); i++) + { + const char *jsonIntStr = stringTable[i]; + RyanJson_t jsonRoot = RyanJsonParse(jsonIntStr); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, jsonIntStr); + + RyanJson_t valueNode = withKey ? RyanJsonGetObjectToKey(jsonRoot, "int32_t") : jsonRoot; + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(valueNode), + withKey ? "Key 'int32_t' not found or not int32_t" : "Root node not int32_t"); + + // 验证解析后的数值是否正确 + int32_t intValue = RyanJsonGetIntValue(valueNode); + TEST_ASSERT_EQUAL_INT32_MESSAGE(IntValueTable[i], intValue, jsonIntStr); + + // 往返校验:序列化后再次解析,整数值应保持一致 + char *serializedStr = RyanJsonPrint(jsonRoot, 128, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(serializedStr, "序列化失败"); + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + + TEST_ASSERT_NOT_NULL(roundtripJson); + RyanJson_t roundtripValueNode = withKey ? RyanJsonGetObjectToKey(roundtripJson, "int32_t") : roundtripJson; + TEST_ASSERT_TRUE(RyanJsonIsInt(roundtripValueNode)); + + int32_t roundtripValue = RyanJsonGetIntValue(roundtripValueNode); + TEST_ASSERT_EQUAL_INT32_MESSAGE(IntValueTable[i], roundtripValue, "往返测试数值不匹配"); + + RyanJsonDelete(roundtripJson); + } +} + +static void testEqualityIntTable(void) +{ + testEqualityIntTableCommon(IntStringTable, RyanJsonTrue); +} + +static void testEqualityIntTable2(void) +{ + testEqualityIntTableCommon(IntStringTable2, RyanJsonFalse); +} + +static void testEqualityIntArrayTable(const char *const *stringTable) +{ + for (uint32_t i = 0; i < sizeof(IntValueTable) / sizeof(IntValueTable[0]); i++) + { + const char *jsonIntStr = stringTable[i]; + RyanJson_t jsonRoot = RyanJsonParse(jsonIntStr); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, jsonIntStr); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsArray(jsonRoot), "Root node not array"); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, RyanJsonGetSize(jsonRoot), "Array size not 1"); + + RyanJson_t valueNode = RyanJsonGetObjectByIndex(jsonRoot, 0); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(valueNode), "Array[0] not int32_t"); + + int32_t intValue = RyanJsonGetIntValue(valueNode); + TEST_ASSERT_EQUAL_INT32_MESSAGE(IntValueTable[i], intValue, jsonIntStr); + + // 往返校验:序列化后再次解析,整数值应保持一致 + char *serializedStr = RyanJsonPrint(jsonRoot, 128, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(serializedStr, "序列化失败"); + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + + TEST_ASSERT_NOT_NULL(roundtripJson); + TEST_ASSERT_TRUE(RyanJsonIsArray(roundtripJson)); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, RyanJsonGetSize(roundtripJson), "Roundtrip array size not 1"); + + RyanJson_t roundtripValueNode = RyanJsonGetObjectByIndex(roundtripJson, 0); + TEST_ASSERT_TRUE(RyanJsonIsInt(roundtripValueNode)); + + int32_t roundtripValue = RyanJsonGetIntValue(roundtripValueNode); + TEST_ASSERT_EQUAL_INT32_MESSAGE(IntValueTable[i], roundtripValue, "往返测试数值不匹配"); + + RyanJsonDelete(roundtripJson); + } +} + +static void testEqualityIntTable3(void) +{ + testEqualityIntArrayTable(IntStringTable3); +} + +void testEqualityIntRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testEqualityIntEdgeCases); + RUN_TEST(testEqualityIntTypeBoundaries); + RUN_TEST(testEqualityIntTable); + RUN_TEST(testEqualityIntTable2); + RUN_TEST(testEqualityIntTable3); +} diff --git a/test/unityTest/cases/equality/testEqualityString.c b/test/unityTest/cases/equality/testEqualityString.c new file mode 100644 index 0000000..d5f785f --- /dev/null +++ b/test/unityTest/cases/equality/testEqualityString.c @@ -0,0 +1,196 @@ +#include "testBase.h" + +/** + * @brief 简单字符串样例(X-macro) + */ +#define SimpleStringList \ + X("") \ + X("hello") \ + X("world") \ + X("test") \ + X("RyanJson") \ + X("123") \ + X("0") \ + X("-1") \ + X("3.14") \ + X("1e10") \ + X("hello world") \ + X("path/to/file") \ + X("abcdefghijklmnopqrstuvwxyz") \ + X("ABCDEFGHIJKLMNOPQRSTUVWXYZ") \ + X("0123456789") \ + X("The quick brown fox jumps over the lazy dog") \ + X(" ") \ + X(" ") \ + X(" leading") \ + X("trailing ") \ + X(" both ") \ + X("true") \ + X("false") \ + X("null") \ + X("@#$%^&*()") \ + X("!@#$%") \ + X("a=b&c=d") \ + X("user@example.com") \ + X("中文测试") \ + X("日本語テスト") \ + X("한국어테스트") \ + X("混合Mixed混合") \ + X("Привет мир") \ + X("مرحبا بالعالم") \ + X("שלום עולם") + +static const char *SimpleStringValueTable[] = { +#define X(a) a, + SimpleStringList +#undef X +}; + +static const char *SimpleStringJsonTable[] = { +#define X(a) "{\"str\":\"" a "\"}", + SimpleStringList +#undef X +}; + +/** + * @brief 转义字符串样例(Json 文本与期望值分离) + */ +typedef struct +{ + const char *json; // Json 字符串(带转义) + const char *expected; // 期望的 C strValue +} EscapeTestCase; + +static const EscapeTestCase EscapeTestCases[] = { + // 制表符 + {"{\"str\":\"hello\\tworld\"}", "hello\tworld"}, + {"{\"str\":\"tab\\there\"}", "tab\there"}, + // 换行符 + {"{\"str\":\"hello\\nworld\"}", "hello\nworld"}, + {"{\"str\":\"line1\\nline2\\nline3\"}", "line1\nline2\nline3"}, + // 回车符 + {"{\"str\":\"hello\\rworld\"}", "hello\rworld"}, + // 引号转义 + {"{\"str\":\"quote\\\"inside\"}", "quote\"inside"}, + {"{\"str\":\"say \\\"hello\\\"\"}", "say \"hello\""}, + // 反斜杠转义 + {"{\"str\":\"backslash\\\\here\"}", "backslash\\here"}, + {"{\"str\":\"C:\\\\Windows\\\\System32\"}", "C:\\Windows\\System32"}, + {"{\"str\":\"path\\\\to\\\\file\"}", "path\\to\\file"}, + // 斜杠(可选转义) + {"{\"str\":\"a\\/b\"}", "a/b"}, + // 退格符 + {"{\"str\":\"back\\bspace\"}", "back\bspace"}, + // 换页符 + {"{\"str\":\"form\\ffeed\"}", "form\ffeed"}, + // 组合转义 + {"{\"str\":\"line1\\nline2\\ttab\"}", "line1\nline2\ttab"}, + {"{\"str\":\"\\\"quoted\\\" and \\\\escaped\\\\\"}", "\"quoted\" and \\escaped\\"}, + // Unicode转义 + {"{\"str\":\"\\u0048\\u0065\\u006C\\u006C\\u006F\"}", "Hello"}, + {"{\"str\":\"\\u4E2D\\u6587\"}", "中文"}, + {"{\"str\":\"euro: \\u20AC\"}", "euro: €"}, + {"{\"str\":\"smile: \\u263A\"}", "smile: ☺"}, +}; + +/** + * @brief 字符串类型边界与类型一致性测试 + */ +void testEqualityStringEdgeCases(void) +{ + // NULL 输入 + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsString(NULL), "RyanJsonIsString(NULL) 应返回 false"); + + // 类型混淆测试 + RyanJson_t num = RyanJsonCreateInt("num", 123); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsString(num), "RyanJsonIsString(Int) 应返回 false"); + RyanJsonDelete(num); + + RyanJson_t dbl = RyanJsonCreateDouble("dbl", 1.23); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsString(dbl), "RyanJsonIsString(Double) 应返回 false"); + RyanJsonDelete(dbl); + + RyanJson_t boolNode = RyanJsonCreateBool("bool", RyanJsonTrue); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsString(boolNode), "RyanJsonIsString(Bool) 应返回 false"); + RyanJsonDelete(boolNode); + + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsString(obj), "RyanJsonIsString(Object) 应返回 false"); + RyanJsonDelete(obj); + + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsString(arr), "RyanJsonIsString(Array) 应返回 false"); + RyanJsonDelete(arr); + + RyanJson_t nullNode = RyanJsonCreateNull("null"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonIsString(nullNode), "RyanJsonIsString(Null) 应返回 false"); + RyanJsonDelete(nullNode); +} + +/** + * @brief 简单字符串解析与往返测试 + */ +static void testEqualityStringSimple(void) +{ + for (uint32_t i = 0; i < sizeof(SimpleStringValueTable) / sizeof(SimpleStringValueTable[0]); i++) + { + const char *jsonStrInput = SimpleStringJsonTable[i]; + RyanJson_t jsonRoot = RyanJsonParse(jsonStrInput); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, "简单字符串解析失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(RyanJsonGetObjectToKey(jsonRoot, "str")), "字段 'str' 不是字符串类型"); + + const char *strValue = RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "str")); + TEST_ASSERT_EQUAL_STRING_MESSAGE(SimpleStringValueTable[i], strValue, jsonStrInput); + + // 往返测试 + char *serializedStr = RyanJsonPrint(jsonRoot, 128, RyanJsonFalse, NULL); + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + TEST_ASSERT_NOT_NULL_MESSAGE(roundtripJson, "简单字符串往返测试:重新解析失败"); + + const char *roundtripValue = RyanJsonGetStringValue(RyanJsonGetObjectToKey(roundtripJson, "str")); + TEST_ASSERT_EQUAL_STRING_MESSAGE(SimpleStringValueTable[i], roundtripValue, "简单字符串往返测试不匹配"); + + RyanJsonDelete(roundtripJson); + } +} + +/** + * @brief 转义字符串解析与往返测试 + */ +static void testEqualityStringEscape(void) +{ + for (uint32_t i = 0; i < sizeof(EscapeTestCases) / sizeof(EscapeTestCases[0]); i++) + { + const EscapeTestCase *tc = &EscapeTestCases[i]; + RyanJson_t jsonRoot = RyanJsonParse(tc->json); + TEST_ASSERT_NOT_NULL_MESSAGE(jsonRoot, tc->json); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(RyanJsonGetObjectToKey(jsonRoot, "str")), "转义字段 'str' 类型错误"); + + const char *strValue = RyanJsonGetStringValue(RyanJsonGetObjectToKey(jsonRoot, "str")); + TEST_ASSERT_EQUAL_STRING_MESSAGE(tc->expected, strValue, tc->json); + + // 往返测试 + char *serializedStr = RyanJsonPrint(jsonRoot, 128, RyanJsonFalse, NULL); + RyanJsonDelete(jsonRoot); + + RyanJson_t roundtripJson = RyanJsonParse(serializedStr); + RyanJsonFree(serializedStr); + TEST_ASSERT_NOT_NULL_MESSAGE(roundtripJson, "转义字符串往返测试:重新解析失败"); + + const char *roundtripValue = RyanJsonGetStringValue(RyanJsonGetObjectToKey(roundtripJson, "str")); + TEST_ASSERT_EQUAL_STRING_MESSAGE(tc->expected, roundtripValue, "转义字符串往返测试不匹配"); + + RyanJsonDelete(roundtripJson); + } +} + +void testEqualityStringRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testEqualityStringEdgeCases); + RUN_TEST(testEqualityStringSimple); + RUN_TEST(testEqualityStringEscape); +} diff --git a/test/unityTest/cases/performance/testDeepRecursion.c b/test/unityTest/cases/performance/testDeepRecursion.c new file mode 100644 index 0000000..ba77fdd --- /dev/null +++ b/test/unityTest/cases/performance/testDeepRecursion.c @@ -0,0 +1,392 @@ +#define _GNU_SOURCE +#include "testBase.h" +#include "FreeRTOS.h" +#include "task.h" + +// 使用轻量 xorshift 保持生成过程可复现且开销更低 +static uint32_t gDeepRandState = 0x9E3779B9U; + +typedef enum +{ + deepJsonModeMixed = 0, + deepJsonModeObjectOnly = 1, + deepJsonModeArrayOnly = 2 +} deepJsonMode_e; + +static deepJsonMode_e gDeepJsonMode = deepJsonModeMixed; +static int32_t gDeepJsonDepth = 10000; +static const size_t gDeepStackThreadWords = 512; + +static const char *deepJsonModeName(deepJsonMode_e mode) +{ + if (deepJsonModeObjectOnly == mode) { return "object-only"; } + if (deepJsonModeArrayOnly == mode) { return "array-only"; } + return "mixed"; +} + +// 辅助:生成 0 到 max-1 的随机数 +static inline uint32_t randomRange(uint32_t max) +{ + if (0U == max) { return 0U; } + + gDeepRandState ^= gDeepRandState << 13; + gDeepRandState ^= gDeepRandState >> 17; + gDeepRandState ^= gDeepRandState << 5; + return gDeepRandState % max; +} + +/** + * @brief 生成深度嵌套的 Json 字符串 + * + * @param depth 目标深度 + * @param outSize 输出生成的字符串长度 + * @return char* 动态分配的字符串,需要调用者 free + */ +static char *generateDeepJson(int32_t depth, size_t *outSize, deepJsonMode_e mode) +{ + /** + * @brief 生成缓冲区预估策略 + * + * 每层结构平均约 4 字节,再叠加少量噪声负载与位图开销,使用 `depth * 6 + 1024` + * 可在控制内存占用的同时,显著降低深层 Json 生成过程中的扩容/越界风险。 + */ + + size_t bufCap = depth * 6 + 1024; + char *jsonStr = (char *)malloc(bufCap); + + // 使用位图替代类型栈,减少辅助空间占用 + // bit=1 表示对象层,bit=0 表示数组层 + uint8_t *typeBitset = (uint8_t *)calloc((depth + 7) / 8, 1); + + if (!jsonStr || !typeBitset) + { + if (jsonStr) { free(jsonStr); } + if (typeBitset) { free(typeBitset); } + return NULL; + } + + char *ptr = jsonStr; + + for (int32_t i = 0; i < depth; i++) + { + // 随机决定当前层类型:对象或数组 + bool isObject = false; + if (deepJsonModeObjectOnly == mode) { isObject = true; } + else if (deepJsonModeArrayOnly == mode) { isObject = false; } + else + { + isObject = randomRange(2); + } + + // 记录类型到位图 + if (isObject) { typeBitset[i / 8] |= (1 << (i % 8)); } + + if (isObject) + { + *ptr++ = '{'; + + // 稀疏噪声注入:仅 5% 概率插入额外字段,兼顾覆盖率与内存占用 + if (deepJsonModeMixed == mode && randomRange(100) < 5) + { + // 随机生成短 key,避免与核心 key "n" 冲突 + char key = (char)('a' + randomRange(13)); // a-m + int32_t type = (int32_t)randomRange(4); + + // 生成简短负载 + if (type == 0) + { + ptr += sprintf(ptr, "\"%c\":%d,", key, randomRange(9)); // int32_t + } + else if (type == 1) + { + ptr += sprintf(ptr, "\"%c\":true,", key); // bool + } + else if (type == 2) + { + ptr += sprintf(ptr, "\"%c\":null,", key); // null + } + else + { + ptr += sprintf(ptr, "\"%c\":\"s\",", key); // string + } + } + + // 核心嵌套 key:"n"(next) + memcpy(ptr, "\"n\":", 4); + ptr += 4; + } + else // 数组层 + { + *ptr++ = '['; + + // 稀疏噪声注入 + if (deepJsonModeMixed == mode && randomRange(100) < 5) + { + int32_t type = (int32_t)randomRange(4); + if (type == 0) { ptr += sprintf(ptr, "%d,", randomRange(9)); } + else if (type == 1) { memcpy(ptr, "false,", 6); } + else if (type == 2) { memcpy(ptr, "null,", 5); } + else + { + memcpy(ptr, "\"v\",", 4); + } + } + } + + // 简单的越界保护 + if ((size_t)(ptr - jsonStr) >= bufCap - 128) + { + testLog("警告:Json 生成缓冲区在深度 %d 处接近耗尽\n", i); + break; + } + } + + // 最内层终点 + memcpy(ptr, "\"end\"", 5); + ptr += 5; + + // 回溯闭合:倒序读取位图并补齐 ] / } + for (int32_t i = depth - 1; i >= 0; i--) + { + int32_t isObject = (typeBitset[i / 8] >> (i % 8)) & 1; + if (isObject) { *ptr++ = '}'; } + else + { + *ptr++ = ']'; + } + } + *ptr = '\0'; + + if (outSize) { *outSize = (size_t)(ptr - jsonStr); } + + free(typeBitset); // 释放位图 + return jsonStr; +} + +/** + * @brief 深度递归与栈占用压力测试 + * + * 在 16KB 栈环境中验证 Parse、Print、Duplicate、Delete 等核心路径是否以迭代方式工作, + * 并在深层嵌套数据下避免栈溢出风险。 + */ +static void deepStackTask(void) +{ + const int32_t depth = gDeepJsonDepth; + const deepJsonMode_e mode = gDeepJsonMode; + size_t jsonLen = 0; + const char *modeName = deepJsonModeName(mode); + + gDeepRandState = 0x9E3779B9U ^ (uint32_t)depth ^ (((uint32_t)mode + 1U) << 24); + testLog("正在生成深度 %d 的测试 Json(mode=%s)...\n", depth, modeName); + char *jsonStr = generateDeepJson(depth, &jsonLen, mode); + TEST_ASSERT_NOT_NULL(jsonStr); + + // 执行 Minify(压缩,迭代路径验证) + testLog("正在运行 Minify...\n"); + RyanJsonMinify(jsonStr, (int32_t)jsonLen); + + // 执行 Parse(解析,迭代路径验证) + testLog("正在运行 Parse,深度 %d ...\n", depth); + RyanJson_t json = RyanJsonParse(jsonStr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "深层嵌套解析失败 (返回了 NULL)"); + free(jsonStr); + + // 执行 Duplicate(复制,迭代路径验证) + testLog("正在运行 Duplicate...\n"); + RyanJson_t dup = RyanJsonDuplicate(json); + TEST_ASSERT_NOT_NULL_MESSAGE(dup, "深层嵌套复制失败 (返回了 NULL)"); + + // 执行 Compare(比较,迭代路径验证) + testLog("正在运行 Compare...\n"); + RyanJsonBool_e eq = RyanJsonCompare(json, dup); + TEST_ASSERT_TRUE_MESSAGE(eq, "深层嵌套比较失败 (结果不相等)"); + RyanJsonDelete(dup); + + // 执行 Print(打印,迭代路径验证) + testLog("正在运行 Print (无格式化)...\n"); + uint32_t len = 0; + char *text = RyanJsonPrint(json, 1024, RyanJsonFalse, &len); + TEST_ASSERT_NOT_NULL_MESSAGE(text, "深层嵌套打印失败 (返回了 NULL)"); + TEST_ASSERT_TRUE_MESSAGE(len == jsonLen, "打印输出长度异常"); + RyanJsonFree(text); + + // 格式化打印 + // 占用堆太大,不够用 + // printf("正在运行 Print (格式化)...\n"); + // text = RyanJsonPrint(json, 1024, RyanJsonTrue, &len); + // TEST_ASSERT_NOT_NULL_MESSAGE(text, "深层嵌套格式化打印失败"); + // RyanJsonFree(text); + + // GetSize(统计节点数量,迭代路径验证) + testLog("正在运行 GetSize...\n"); + uint32_t size = RyanJsonGetSize(json); + TEST_ASSERT_TRUE(size > 0); + + // 深层遍历与修改测试 + testLog("正在运行深度遍历...\n"); + RyanJson_t current = json; + RyanJson_t parent = NULL; + int32_t actualDepth = 0; + + // 逐层向下遍历到最深节点 + while (current) + { + if (RyanJsonIsObject(current)) + { + parent = current; + current = RyanJsonGetObjectByKey(current, "n"); + } + else if (RyanJsonIsArray(current)) + { + // 对于本测试生成的结构,下一个层级(或负载)总是数组最后一个元素。 + // 旧逻辑只查找 Object/Array,末层为 "end"(String/Number)时会少算 1 层。 + RyanJson_t child = RyanJsonGetObjectValue(current); + if (child) + { + while (RyanJsonGetNext(child)) + { + child = RyanJsonGetNext(child); + } + // child 此时为最后一个节点(容器或负载) + parent = current; + current = child; + } + else + { + // 生成器理论上不会产出空数组,这里保留防御性分支 + current = NULL; + } + } + else + { + break; // 已到达负载节点 + } + + if (current) { actualDepth++; } + } + + testLog("实际遍历深度: %d (预期 %d)\n", actualDepth, depth); + TEST_ASSERT_TRUE_MESSAGE(actualDepth == depth, "遍历深度严重偏离预期"); + TEST_ASSERT_NOT_NULL(parent); + + // 深层插入 + testLog("正在运行深层插入...\n"); + RyanJsonBool_e insertOk = RyanJsonFalse; + if (RyanJsonIsObject(parent)) { insertOk = RyanJsonAddStringToObject(parent, "inserted_key", "val"); } + else if (RyanJsonIsArray(parent)) + { + RyanJson_t newItem = RyanJsonCreateString(NULL, "val"); + insertOk = RyanJsonInsert(parent, 0, newItem); + if (RyanJsonFalse == insertOk) { RyanJsonDelete(newItem); } + } + TEST_ASSERT_TRUE_MESSAGE(insertOk, "Deep Insert 失败"); + + // 深层替换 + testLog("正在运行深层替换...\n"); + RyanJson_t replaceItem = RyanJsonCreateInt("replaced", 999); + if (RyanJsonIsObject(parent)) + { + // RyanJsonReplaceByKey 会替换 key 为 "inserted_key" 的节点 + if (RyanJsonFalse == RyanJsonReplaceByKey(parent, "inserted_key", replaceItem)) + { + RyanJsonDelete(replaceItem); + replaceItem = NULL; + } + } + else if (RyanJsonIsArray(parent)) + { + if (RyanJsonFalse == RyanJsonReplaceByIndex(parent, 0, replaceItem)) + { + RyanJsonDelete(replaceItem); + replaceItem = NULL; + } + } + else + { + RyanJsonDelete(replaceItem); + replaceItem = NULL; + } + + // 深层分离 + testLog("正在运行深层分离...\n"); + RyanJson_t detached = NULL; + if (RyanJsonIsObject(parent)) { detached = RyanJsonDetachByKey(parent, "inserted_key"); } + else if (RyanJsonIsArray(parent)) { detached = RyanJsonDetachByIndex(parent, 0); } + + if (detached) { RyanJsonDelete(detached); } + else + { + testLog("警告:Detach 返回了 NULL。\n"); + } + + // 删除整棵 Json 树 + testLog("正在运行 Delete Json...\n"); + RyanJsonDelete(json); +} + +typedef struct +{ + int32_t depth; + deepJsonMode_e mode; + uint8_t isTestProtectPassed; +} deepStressThreadCtx_t; + +static void deepStressThreadTask(void *arg) +{ + deepStressThreadCtx_t *threadCtx = (deepStressThreadCtx_t *)arg; + const char *modeName = "mixed"; + TaskHandle_t currentTask = NULL; + char taskName[64] = {0}; + + if (NULL == threadCtx) { return; } + threadCtx->isTestProtectPassed = 0U; + gDeepJsonDepth = threadCtx->depth; + gDeepJsonMode = threadCtx->mode; + + if (TEST_PROTECT()) + { + deepStackTask(); + threadCtx->isTestProtectPassed = 1U; + } + + modeName = deepJsonModeName(threadCtx->mode); + testLog("[deep] 用例参数: 模式=%s, 深度=%ld\n", modeName, (long)threadCtx->depth); + (void)snprintf(taskName, sizeof(taskName), "deep-%s", modeName); + currentTask = xTaskGetCurrentTaskHandle(); + logTaskStackRuntimeInfoByHandle("deep", taskName, currentTask); +} + +static void runDeepStackUsageStressCase(int32_t depth, deepJsonMode_e mode) +{ + int32_t runRet = 0; + deepStressThreadCtx_t threadCtx = {0}; + + threadCtx.depth = depth; + threadCtx.mode = mode; + runRet = testPlatformRunThreadWithStackSize(deepStressThreadTask, &threadCtx, gDeepStackThreadWords); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, runRet, "deep 测试线程启动失败"); + TEST_ASSERT_TRUE_MESSAGE(0U != threadCtx.isTestProtectPassed, "deep 测试线程执行失败"); +} + +static void testDeepStackUsageStress(void) +{ + runDeepStackUsageStressCase(10000, deepJsonModeMixed); +} + +static void testDeepStackUsageObjectOnlyStress(void) +{ + runDeepStackUsageStressCase(10000, deepJsonModeObjectOnly); +} + +static void testDeepStackUsageArrayOnlyStress(void) +{ + runDeepStackUsageStressCase(10000, deepJsonModeArrayOnly); +} + +void testDeepRecursionRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testDeepStackUsageStress); + RUN_TEST(testDeepStackUsageObjectOnlyStress); + RUN_TEST(testDeepStackUsageArrayOnlyStress); +} diff --git a/test/unityTest/cases/performance/testMemory.c b/test/unityTest/cases/performance/testMemory.c new file mode 100644 index 0000000..64f6014 --- /dev/null +++ b/test/unityTest/cases/performance/testMemory.c @@ -0,0 +1,294 @@ +#include "../../common/testCommon.h" + +static void *yyMalloc(void *ctx, size_t size) +{ + (void)ctx; + return unityTestMalloc(size); +} + +static void *yyRealloc(void *ctx, void *ptr, size_t oldSize, size_t size) +{ + (void)ctx; + (void)oldSize; + return unityTestRealloc(ptr, size); +} + +static void yyFree(void *ctx, void *ptr) +{ + (void)ctx; + unityTestFree(ptr); +} + +static void RyanJsonMemoryFootprint(const char *jsonstr, int32_t *footprint) +{ + int32_t baselineUse = unityTestGetUse(); + RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc); + + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "RyanJson 解析失败"); + + *footprint = unityTestGetUse() - baselineUse; + RyanJsonDelete(json); + TEST_ASSERT_EQUAL_INT_MESSAGE(baselineUse, unityTestGetUse(), "RyanJson 释放后 TLSF used 未回到基线"); +} + +static void cJSONMemoryFootprint(const char *jsonstr, int32_t *footprint) +{ + int32_t baselineUse = unityTestGetUse(); + cJSON_Hooks hooks = {.malloc_fn = unityTestMalloc, .free_fn = unityTestFree}; + cJSON_InitHooks(&hooks); + + cJSON *json = cJSON_Parse(jsonstr); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "cJSON 解析失败"); + + *footprint = unityTestGetUse() - baselineUse; + cJSON_Delete(json); + TEST_ASSERT_EQUAL_INT_MESSAGE(baselineUse, unityTestGetUse(), "cJSON 释放后 TLSF used 未回到基线"); +} + +static void yyjsonMemoryFootprint(const char *jsonstr, int32_t *footprint) +{ + int32_t baselineUse = unityTestGetUse(); + yyjson_alc yyalc = {yyMalloc, yyRealloc, yyFree, NULL}; + + // 先解析成只读文档 + yyjson_doc *doc = yyjson_read_opts(jsonstr, strlen(jsonstr), YYJSON_READ_NOFLAG, &yyalc, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(doc, "yyjson 解析只读文档失败"); + + // 从只读文档拷贝为可变文档 + yyjson_mut_doc *mdoc = yyjson_doc_mut_copy(doc, &yyalc); + yyjson_doc_free(doc); + TEST_ASSERT_NOT_NULL_MESSAGE(mdoc, "yyjson 拷贝可变文档失败"); + + *footprint = unityTestGetUse() - baselineUse; + yyjson_mut_doc_free(mdoc); + TEST_ASSERT_EQUAL_INT_MESSAGE(baselineUse, unityTestGetUse(), "yyjson 释放后 TLSF used 未回到基线"); +} + +static void printfJsonCompare(const char *title, const char *jsonstr) +{ + int32_t ryanJsonCount = 0; + int32_t cJSONCount = 0; + int32_t yyjsonCount = 0; + + RyanJsonMemoryFootprint(jsonstr, &ryanJsonCount); + cJSONMemoryFootprint(jsonstr, &cJSONCount); + yyjsonMemoryFootprint(jsonstr, &yyjsonCount); + + (void)testLog("%s 原始长度: %lu, 模拟分配(header=%lu,align=%lu) 峰值占用 -> RyanJson:%ld, cJSON:%ld, yyjson:%ld\r\n", title, + (unsigned long)strlen(jsonstr), (unsigned long)RyanJsonTestAllocHeaderSize, (unsigned long)RyanJsonTestAllocAlignSize, + (long)ryanJsonCount, (long)cJSONCount, (long)yyjsonCount); + + TEST_ASSERT_TRUE_MESSAGE(ryanJsonCount > 0 && cJSONCount > 0 && yyjsonCount > 0, "内存统计值必须大于 0"); + TEST_ASSERT_TRUE_MESSAGE(ryanJsonCount < cJSONCount, "RyanJson 内存占用应低于 cJSON"); + if (ryanJsonCount >= yyjsonCount) + { + (void)testLog("提示: 当前模拟参数下 RyanJson(%ld) 未低于 yyjson(%ld)\r\n", (long)ryanJsonCount, (long)yyjsonCount); + } + + if (cJSONCount > 0 && yyjsonCount > 0) + { + double saveVsCjson = 100.0 - ((double)ryanJsonCount * 100.0) / (double)cJSONCount; + double saveVsYyjson = 100.0 - ((double)ryanJsonCount * 100.0) / (double)yyjsonCount; + (void)testLog("内存优化程度 -> 比 cJSON 节省: \033[1;32m%.2f%%\033[0m, 比 yyjson 节省: \033[1;32m%.2f%%\033[0m\r\n", + saveVsCjson, saveVsYyjson); + } +} + +static void testMixedJsonMemory(void) +{ + static const char jsonstr[] = + "{\"item1\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," + "\"item\":{\"inter\":16," + "\"double\":16.89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89," + "16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\",\"hello\"," + "\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16,\"double\":16.89," + "\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false," + "\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]" + "},\"item2\":{" + "\"inter\":16,\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":{\"inter\":16,\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":" + "true,\"boolFalse\":false,\"null\":null}," + "\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89],\"arrayString\":[\"hello\",\"hello\"," + "\"hello\",\"hello\"," + "\"hello\"],\"array\":[16,16.89,\"hello\"," + "true,false,null],\"arrayItem\":[{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":" + "false,\"null\":null},{" + "\"inter\":16,\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]},\"item3\":{\"inter\":16,\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":" + "true,\"boolFalse\":false,\"null\":null," + "\"item\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}," + "\"arrayInt\":[16,16,16,16," + "16],\"arrayDouble\":[16.89,16.89,16.89," + "16.89,16.89],\"arrayString\":[\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true," + "false,null]," + "\"arrayItem\":[{\"inter\":16,\"double\":16.89," + "\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":" + "true,\"boolFalse\":false,\"null\":null}]}" + ",\"item4\":{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null," + "\"item\":{\"inter\":16," + "\"double\":16.89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89," + "16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\",\"hello\"," + "\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16,\"double\":16.89," + "\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false," + "\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}]" + "}}"; + printfJsonCompare("混合对象", jsonstr); +} + +static void testObjectJsonMemory(void) +{ + static const char jsonstr[] = + "{\"message\":\"success感谢又拍云(upyun.com)提供CDN赞助\",\"status\":200,\"date\":\"20230822\",\"time\":\"2023-08-22 " + "09:44:54\",\"cityInfo\":{\"city\":\"郑州市\",\"citykey\":\"101180101\",\"parent\":\"河南\",\"updateTime\":\"07:46\"}," + "\"data\":{\"shidu\":" + "\"85%\",\"pm25\":20,\"pm10\":56," + "\"quality\":\"良\",\"wendu\":\"29\",\"ganmao\":\"极少数敏感人群应减少户外活动\",\"forecast\":[{\"date\":\"22\",\"high\":" + "\"高温 " + "35℃\",\"low\":\"低温 " + "23℃\",\"ymd\":\"2023-08-22\",\"week\":\"星期二\",\"sunrise\":\"05:51\",\"sunset\":\"19:05\",\"aqi\":78,\"fx\":\"东南风\"," + "\"fl\":\"2级\"," + "\"type\":\"晴\",\"notice\":" + "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"23\",\"high\":\"高温 33℃\",\"low\":\"低温 " + "23℃\",\"ymd\":\"2023-08-23\",\"week\":\"星期三\",\"sunrise\":\"05:52\",\"sunset\":\"19:04\",\"aqi\":71,\"fx\":\"南风\"," + "\"fl\":\"2级\"," + "\"type\":\"中雨\",\"notice\":" + "\"记得随身携带雨伞哦\"},{\"date\":\"24\",\"high\":\"高温 31℃\",\"low\":\"低温 " + "21℃\",\"ymd\":\"2023-08-24\",\"week\":\"星期四\",\"sunrise\":\"05:52\",\"sunset\":\"19:03\",\"aqi\":74,\"fx\":\"东风\"," + "\"fl\":\"2级\"," + "\"type\":\"晴\",\"notice\":" + "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"25\",\"high\":\"高温 30℃\",\"low\":\"低温 " + "23℃\",\"ymd\":\"2023-08-25\",\"week\":\"星期五\",\"sunrise\":\"05:53\",\"sunset\":\"19:02\",\"aqi\":93,\"fx\":\"东风\"," + "\"fl\":\"1级\"," + "\"type\":\"小雨\",\"notice\":" + "\"雨虽小,注意保暖别感冒\"},{\"date\":\"26\",\"high\":\"高温 25℃\",\"low\":\"低温 " + "22℃\",\"ymd\":\"2023-08-26\",\"week\":\"星期六\",\"sunrise\":\"05:54\",\"sunset\":\"19:00\",\"aqi\":80,\"fx\":\"东北风\"," + "\"fl\":\"1级\"," + "\"type\":\"阴\",\"notice\":" + "\"不要被阴云遮挡住好心情\"},{\"date\":\"27\",\"high\":\"高温 27℃\",\"low\":\"低温 " + "20℃\",\"ymd\":\"2023-08-27\",\"week\":\"星期日\",\"sunrise\":\"05:55\",\"sunset\":\"18:59\",\"aqi\":74,\"fx\":\"西北风\"," + "\"fl\":\"1级\"," + "\"type\":\"阴\",\"notice\":" + "\"不要被阴云遮挡住好心情\"},{\"date\":\"28\",\"high\":\"高温 30℃\",\"low\":\"低温 " + "20℃\",\"ymd\":\"2023-08-28\",\"week\":\"星期 " + "一\",\"sunrise\":\"05:55\",\"sunset\":\"18:58\",\"aqi\":80,\"fx\":\"东北风\",\"fl\":\"2级\",\"type\":\"多云\",\"notice\":" + "\"阴晴之间,谨防紫外线侵扰\"},{\"date\":\"29\",\"high\":" + "\"高温 30℃\",\"low\":\"低温 " + "20℃\",\"ymd\":\"2023-08-29\",\"week\":\"星期二\",\"sunrise\":\"05:56\",\"sunset\":\"18:56\",\"aqi\":80,\"fx\":\"东北风\"," + "\"fl\":\"2级\"," + "\"type\":\"多云\",\"notice\":" + "\"阴晴之间,谨防紫外线侵扰\"},{\"date\":\"30\",\"high\":\"高温 31℃\",\"low\":\"低温 " + "20℃\",\"ymd\":\"2023-08-30\",\"week\":\"星期三\",\"sunrise\":\"05:57\",\"sunset\":\"18:55\",\"aqi\":92,\"fx\":\"南风\"," + "\"fl\":\"1级\"," + "\"type\":\"晴\",\"notice\":" + "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"31\",\"high\":\"高温 33℃\",\"low\":\" 低温 " + "22℃\",\"ymd\":\"2023-08-31\",\"week\":\"星期四\",\"sunrise\":\"05:57\",\"sunset\":\"18:54\",\"aqi\":91,\"fx\":\"南风\"," + "\"fl\":\"1级\"," + "\"type\":\"晴\",\"notice\":" + "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"01\",\"high\":\"高温 34℃\",\"low\":\"低温 " + "23℃\",\"ymd\":\"2023-09-01\",\"week\":\"星期五\",\"sunrise\":\"05:58\",\"sunset\":\"18:52\",\"aqi\":91,\"fx\":\"西风\"," + "\"fl\":\"1级\"," + "\"type\":\"晴\",\"notice\":" + "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"02\",\"high\":\"高温 36℃\",\"low\":\"低温 " + "25℃\",\"ymd\":\"2023-09-02\",\"week\":\"星期六\",\"sunrise\":\"05:59\",\"sunset\":\"18:51\",\"aqi\":78,\"fx\":\"南风\"," + "\"fl\":\"1级\"," + "\"type\":\"阴\",\"notice\":" + "\"不要被阴云遮挡住好心情\"},{\"date\":\"03\",\"high\":\"高温 35℃\",\"low\":\"低温 " + "24℃\",\"ymd\":\"2023-09-03\",\"week\":\"星期日\",\"sunrise\":\"06:00\",\"sunset\":\"18:50\",\"aqi\":82,\"fx\":\"东北风\"," + "\"fl\":\"1级\"," + "\"type\":\"晴\",\"notice\":" + "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"04\",\"high\":\"高温 35℃\",\"low\":\"低温 " + "25℃\",\"ymd\":\"2023-09-04\",\"week\":\"星期一\",\"sunrise\":\"06:00\",\"sunset\":\"18:48\",\"aqi\":88,\"fx\":\"南风\"," + "\"fl\":\"2级\"," + "\"type\":\"晴\",\"notice\":" + "\"愿你拥有比阳光明媚的心情\"},{\"date\":\"05\",\"high\":\"高温 35℃\",\"low\":\"低温 " + "25℃\",\"ymd\":\"2023-09-05\",\"week\":\"星期二\",\"sunrise\":\"06:01\",\"sunset\":\"18:47\",\"aqi\":58,\"fx\":\"南风\"," + "\"fl\":\"2级\"," + "\"type\":\"阴\",\"notice\":" + "\"不要被阴云遮挡住好心情\"}],\"yesterday\":{\"date\":\"21\",\"high\":\"高温 34℃\",\"low\":\"低温 " + "24℃\",\"ymd\":\"2023-08-21\",\"week\":\" " + "星期一\",\"sunrise\":\"05:50\",\"sunset\":\"19:07\",\"aqi\":60,\"fx\":\"西风\",\"fl\":\"2级\",\"type\":\"小雨\"," + "\"notice\":" + "\"雨虽小,注意保暖别感冒\"}}}"; + printfJsonCompare("经典天气对象", jsonstr); +} + +static void testArrayJsonMemory(void) +{ + static const char jsonstr[] = + "{\"item1\":{\"arrayInt\":[16,16,16,16,16,16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89,16.89,16.89,16." + "89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\",\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true," + "false,null,16,16.89," + "\"hello\",true,false,null]},\"item2\":{" + "\"arrayInt\":[16,16,16,16,16,16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16." + "89],\"arrayString\":[" + "\"hello\",\"hello\",\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null,16,16.89," + "\"hello\",true,false," + "null]},\"item3\":{\"arrayInt\":[16,16,16," + "16,16,16,16,16,16,16],\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89],\"arrayString\":[" + "\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null,16,16.89,\"hello\",true,false," + "null]},\"item4\":{" + "\"arrayInt\":[16,16,16,16,16,16,16,16,16,16]," + "\"arrayDouble\":[16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89,16.89],\"arrayString\":[\"hello\",\"hello\"," + "\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\",\"hello\"," + "\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null,16,16.89,\"hello\",true,false,null]}}"; + printfJsonCompare("深度数组", jsonstr); +} + +static void testSmallMixedJsonMemory(void) +{ + static const char jsonstr[] = + "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null}"; + printfJsonCompare("小型混合对象", jsonstr); +} + +static void testSmallStringJsonMemory(void) +{ + static const char jsonstr[] = + "{\"inter\":\"16\",\"double\":\"16.89\",\"string\":\"hello\",\"boolTrue\":\"true\",\"boolFalse\":\"false\",\"null\":" + "\"null\"}"; + printfJsonCompare("小型字符串对象", jsonstr); +} + +static void testCompressedBusinessJsonMemory(void) +{ + static const char jsonstr[] = + "{\"0\":\"0\",\"1\":\"189774523\",\"2\":{\"7\":\"3\",\"8\":\"103\",\"9\":\"37\",\"20\":\"0\",\"26\":\"37\",\"27\":" + "\"367\",\"28\":\"367\",\"s\":\"0\",\"t\":\"0\",\"a\":\"24.98\",\"2a\":\"0\",\"1p\":\"23628\"},\"3\":\"0\",\"22\":" + "\"epmgrow1105\",\"23\":\"0\",\"29\":\"0\",\"i\":\"4\",\"b\":\"900\",\"c\":\"1\",\"rsrp\":\"-111\",\"rsrq\":\"-4\"," + "\"sinr\":\"0\",\"soc\":\"XXXXXXX\",\"j\":\"0\",\"g\":\"898604asdf0210\",\"h\":\"866968798839\",\"d\":\"1.3.5." + "00.20991231\",\"f\":\"0\",\"k\":\"1\",\"l\":\"20000\",\"m\":\"20000\",\"u\":\"0\",\"v\":\"0\",\"e\":\"1\",\"w\":\"0." + "00\",\"n\":\"0\",\"2h\":\"0\",\"o\":\"30\",\"1v\":\"12000\",\"2c\":\"0\",\"p\":\"1\",\"q\":\"1\",\"x\":\"0\",\"y\":" + "\"167\",\"r\":\"0\",\"1x\":\"0\",\"1w\":\"0\",\"1y\":\"100.00\",\"1u\":\"0\"}"; + printfJsonCompare("压缩业务对象", jsonstr); +} + +void testMemoryRunner(void) +{ + UnitySetTestFile(__FILE__); + unityTestSetAllocSimulation(1U); + + RUN_TEST(testMixedJsonMemory); + RUN_TEST(testObjectJsonMemory); + RUN_TEST(testArrayJsonMemory); + RUN_TEST(testSmallMixedJsonMemory); + RUN_TEST(testSmallStringJsonMemory); + RUN_TEST(testCompressedBusinessJsonMemory); + + unityTestSetAllocSimulation(0U); +} diff --git a/test/unityTest/cases/performance/testStress.c b/test/unityTest/cases/performance/testStress.c new file mode 100644 index 0000000..1f068fc --- /dev/null +++ b/test/unityTest/cases/performance/testStress.c @@ -0,0 +1,277 @@ +#include "testBase.h" + +/** + * @brief 压力与边界测试 + * + * @note 验证 RyanJson 处理大数据量(长字符串、大数组)的能力。 + */ +static RyanJson_t createSequentialIntArray(uint32_t arraySize) +{ + RyanJson_t array = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(array); + + for (uint32_t i = 0; i < arraySize; i++) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddIntToArray(array, (int32_t)i), "向大数组添加整数失败"); + } + + return array; +} + +static int32_t gStressFailAfter = -1; +static int32_t gStressAllocCount = 0; + +static void *stressFailMalloc(size_t size) +{ + if (gStressFailAfter >= 0 && gStressAllocCount++ >= gStressFailAfter) { return NULL; } + return unityTestMalloc(size); +} + +static void *stressFailRealloc(void *block, size_t size) +{ + if (gStressFailAfter >= 0 && gStressAllocCount++ >= gStressFailAfter) { return NULL; } + return unityTestRealloc(block, size); +} + +static void stressSetFailAfter(int32_t failAfter) +{ + gStressFailAfter = failAfter; + gStressAllocCount = 0; + RyanJsonInitHooks(stressFailMalloc, unityTestFree, stressFailRealloc); +} + +static void stressRestoreHooks(void) +{ + RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc); + gStressFailAfter = -1; + gStressAllocCount = 0; +} + +static void testStressLongString(void) +{ + // 测试超长字符串 (10KB) + const int32_t longStrLen = 10 * 1024; + char *longStrVal = (char *)malloc(longStrLen + 1); + TEST_ASSERT_NOT_NULL(longStrVal); + memset(longStrVal, 'A', longStrLen); + longStrVal[longStrLen] = '\0'; + + RyanJson_t jsonStr = RyanJsonCreateString("longStr", longStrVal); + TEST_ASSERT_NOT_NULL(jsonStr); + TEST_ASSERT_EQUAL_STRING_MESSAGE(longStrVal, RyanJsonGetStringValue(jsonStr), "超长字符串读取不匹配"); + + RyanJsonDelete(jsonStr); + free(longStrVal); +} + +static void testStressLargeArray(void) +{ + // 测试大数组 (1000 个整数) + const uint32_t arraySize = 1000; + RyanJson_t array = createSequentialIntArray(arraySize); + + TEST_ASSERT_EQUAL_UINT32_MESSAGE(arraySize, (uint32_t)RyanJsonGetArraySize(array), "大数组长度错误"); + + // 校验最后一个元素 + RyanJson_t lastItem = RyanJsonGetObjectByIndex(array, (int32_t)(arraySize - 1U)); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT32_MESSAGE(0, RyanJsonGetIntValue(lastItem), "大数组末尾元素错误"); +#else + TEST_ASSERT_EQUAL_INT32_MESSAGE((int32_t)(arraySize - 1U), RyanJsonGetIntValue(lastItem), "大数组末尾元素错误"); +#endif + + RyanJsonDelete(array); +} + +static void testStressPrint(void) +{ + // 序列化压力测试 (无格式) + const uint32_t arraySize = 1000; + RyanJson_t array = createSequentialIntArray(arraySize); + + char *printed = RyanJsonPrint(array, 8192, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(printed, "大数组序列化失败"); + + RyanJsonFree(printed); + RyanJsonDelete(array); +} + +static void testStressLargeArrayRoundtrip(void) +{ + const uint32_t arraySize = 2048; + RyanJson_t array = createSequentialIntArray(arraySize); + + // 关键位置抽样检查,避免仅检查尾节点导致漏检 + const uint32_t sampleIndex[] = {0U, 1U, 2U, 127U, 1023U, 1536U, 2047U}; + for (uint32_t i = 0; i < sizeof(sampleIndex) / sizeof(sampleIndex[0]); i++) + { + uint32_t idx = sampleIndex[i]; + RyanJson_t item = RyanJsonGetObjectByIndex(array, (int32_t)idx); + TEST_ASSERT_NOT_NULL_MESSAGE(item, "大数组抽样索引越界"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(item), "大数组抽样元素类型错误"); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT32_MESSAGE((int32_t)(arraySize - 1U - idx), RyanJsonGetIntValue(item), "大数组抽样元素值错误"); +#else + TEST_ASSERT_EQUAL_INT32_MESSAGE((int32_t)idx, RyanJsonGetIntValue(item), "大数组抽样元素值错误"); +#endif + } + + uint32_t printLen = 0; + char *printed = RyanJsonPrint(array, 0, RyanJsonFalse, &printLen); + TEST_ASSERT_NOT_NULL_MESSAGE(printed, "大数组往返测试:序列化失败"); + TEST_ASSERT_TRUE_MESSAGE(printLen > arraySize, "大数组序列化长度异常"); + + RyanJson_t parsed = RyanJsonParse(printed); + TEST_ASSERT_NOT_NULL_MESSAGE(parsed, "大数组往返测试:反序列化失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(array, parsed), "大数组往返测试:前后 Compare 不一致"); + + RyanJsonDelete(parsed); + RyanJsonFree(printed); + RyanJsonDelete(array); +} + +static void testStressLargeStringArrayPreallocated(void) +{ + const uint32_t arraySize = 1200; + RyanJson_t array = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(array); + + for (uint32_t i = 0; i < arraySize; i++) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddStringToArray(array, "v"), "向字符串大数组添加元素失败"); + } + + uint32_t expectLen = 0; + char *expect = RyanJsonPrint(array, 0, RyanJsonFalse, &expectLen); + TEST_ASSERT_NOT_NULL_MESSAGE(expect, "字符串大数组序列化失败"); + + char *buf = (char *)malloc((size_t)expectLen + 1U); + TEST_ASSERT_NOT_NULL(buf); + + char *out = RyanJsonPrintPreallocated(array, buf, expectLen + 1U, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(out, "字符串大数组预分配刚好够用应成功"); + TEST_ASSERT_EQUAL_STRING_MESSAGE(expect, out, "字符串大数组预分配输出不一致"); + + RyanJson_t parsed = RyanJsonParse(out); + TEST_ASSERT_NOT_NULL_MESSAGE(parsed, "字符串大数组预分配结果解析失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(array, parsed), "字符串大数组预分配结果前后 Compare 不一致"); + + RyanJsonDelete(parsed); + free(buf); + RyanJsonFree(expect); + RyanJsonDelete(array); +} + +static void testStressLargeStringArrayPreallocatedNoTerminator(void) +{ + const uint32_t arraySize = 800; + RyanJson_t array = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(array); + + for (uint32_t i = 0; i < arraySize; i++) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddStringToArray(array, "value"), "向字符串大数组添加元素失败"); + } + + uint32_t expectLen = 0; + char *expect = RyanJsonPrint(array, 0, RyanJsonFalse, &expectLen); + TEST_ASSERT_NOT_NULL(expect); + + char *buf = (char *)malloc((size_t)expectLen); + TEST_ASSERT_NOT_NULL(buf); + + // 少 1 字节 '\0' 空间,预分配打印应失败 + char *out = RyanJsonPrintPreallocated(array, buf, expectLen, RyanJsonFalse, NULL); + TEST_ASSERT_NULL_MESSAGE(out, "预分配缺少 '\\0' 空间应失败"); + + free(buf); + RyanJsonFree(expect); + RyanJsonDelete(array); +} + +static void testStressPrintOomLargeArray(void) +{ + RyanJson_t array = createSequentialIntArray(2000); + + // 首次分配失败 + stressSetFailAfter(0); + char *printed = RyanJsonPrint(array, 0, RyanJsonFalse, NULL); + stressRestoreHooks(); + if (printed) { RyanJsonFree(printed); } + TEST_ASSERT_NULL_MESSAGE(printed, "Print OOM(首次分配失败) 应返回 NULL"); + + // 首次分配成功,扩容失败 + stressSetFailAfter(1); + printed = RyanJsonPrint(array, 1, RyanJsonFalse, NULL); + stressRestoreHooks(); + if (printed) { RyanJsonFree(printed); } + TEST_ASSERT_NULL_MESSAGE(printed, "Print OOM(扩容失败) 应返回 NULL"); + + RyanJsonDelete(array); +} + +static void testStressLargeObjectKeyLookup(void) +{ + const uint32_t keyCount = 1500; + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + + for (uint32_t i = 0; i < keyCount; i++) + { + char key[24]; + RyanJsonSnprintf(key, sizeof(key), "k%04u", (unsigned)i); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddIntToObject(obj, key, (int32_t)i), "大对象添加 key 失败"); + } + + TEST_ASSERT_EQUAL_UINT32_MESSAGE(keyCount, (uint32_t)RyanJsonGetSize(obj), "大对象大小错误"); + + // 热点索引查找:头/中/尾 + const uint32_t lookupIndex[] = {0U, keyCount / 2U, keyCount - 1U}; + for (uint32_t i = 0; i < sizeof(lookupIndex) / sizeof(lookupIndex[0]); i++) + { + char key[24]; + uint32_t idx = lookupIndex[i]; + RyanJsonSnprintf(key, sizeof(key), "k%04u", (unsigned)idx); + RyanJson_t item = RyanJsonGetObjectByKey(obj, key); + TEST_ASSERT_NOT_NULL_MESSAGE(item, "大对象 key 查找失败"); + TEST_ASSERT_EQUAL_INT32_MESSAGE((int32_t)idx, RyanJsonGetIntValue(item), "大对象 key 查找值错误"); + } + + // 替换中间节点并验证 + { + char midKey[24]; + uint32_t mid = keyCount / 2U; + RyanJsonSnprintf(midKey, sizeof(midKey), "k%04u", (unsigned)mid); + RyanJson_t replaceItem = RyanJsonCreateInt(midKey, -12345); + TEST_ASSERT_NOT_NULL(replaceItem); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonReplaceByKey(obj, midKey, replaceItem), "大对象中间节点替换失败"); + TEST_ASSERT_EQUAL_INT32_MESSAGE(-12345, RyanJsonGetIntValue(RyanJsonGetObjectByKey(obj, midKey)), + "大对象中间节点替换值错误"); + } + + uint32_t len = 0; + char *printed = RyanJsonPrint(obj, 0, RyanJsonFalse, &len); + TEST_ASSERT_NOT_NULL_MESSAGE(printed, "大对象序列化失败"); + TEST_ASSERT_TRUE_MESSAGE(len > keyCount * 6U, "大对象序列化长度异常"); + + RyanJson_t roundtrip = RyanJsonParse(printed); + TEST_ASSERT_NOT_NULL_MESSAGE(roundtrip, "大对象往返测试:解析失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompare(obj, roundtrip), "大对象往返测试:前后 Compare 不一致"); + + RyanJsonDelete(roundtrip); + RyanJsonFree(printed); + RyanJsonDelete(obj); +} + +void testStressRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testStressLongString); + RUN_TEST(testStressLargeArray); + RUN_TEST(testStressPrint); + RUN_TEST(testStressLargeArrayRoundtrip); + RUN_TEST(testStressLargeStringArrayPreallocated); + RUN_TEST(testStressLargeStringArrayPreallocatedNoTerminator); + RUN_TEST(testStressPrintOomLargeArray); + RUN_TEST(testStressLargeObjectKeyLookup); +} diff --git a/test/unityTest/cases/utils/testPrint.c b/test/unityTest/cases/utils/testPrint.c new file mode 100644 index 0000000..7e1a3f5 --- /dev/null +++ b/test/unityTest/cases/utils/testPrint.c @@ -0,0 +1,501 @@ +#include "testBase.h" + +static RyanJson_t createObjectWithIntMember(const char *key, int32_t value) +{ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, key, value)); + return obj; +} + +static RyanJsonPrintStyle makeStyle(const char *indent, const char *newline, uint8_t spaceAfterColon, RyanJsonBool_e format) +{ + RyanJsonPrintStyle style = { + .indent = indent, + .indentLen = (uint32_t)strlen(indent), + .newline = newline, + .newlineLen = (uint32_t)strlen(newline), + .spaceAfterColon = spaceAfterColon, + .format = format, + }; + return style; +} + +void testPrintStyleEdgeCases(void) +{ + // NULL 输入 + TEST_ASSERT_NULL(RyanJsonPrint(NULL, 10, RyanJsonTrue, NULL)); + + RyanJson_t json = RyanJsonCreateObject(); + TEST_ASSERT_NULL(RyanJsonPrintWithStyle(json, 10, NULL, NULL)); + + // format=false 但提供打印风格 + RyanJsonPrintStyle style = makeStyle(" ", "\n", 1, RyanJsonFalse); + char *minified = RyanJsonPrintWithStyle(json, 10, &style, NULL); + TEST_ASSERT_NOT_NULL(minified); + TEST_ASSERT_EQUAL_STRING("{}", minified); // 应为压缩输出,不包含空格与换行 + RyanJsonFree(minified); + + // 极端小缓冲区(验证内部自动扩容) + // RyanJsonPrint 内部会强制最小缓冲区尺寸,这里只需验证行为正确且不崩溃 + RyanJsonAddIntToObject(json, "a", 1); + char *smallBufPrint = RyanJsonPrint(json, 1, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL(smallBufPrint); + TEST_ASSERT_EQUAL_STRING("{\"a\":1}", smallBufPrint); + RyanJsonFree(smallBufPrint); + + RyanJsonDelete(json); +} + +static void testPrintPreallocatedTooSmall(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + RyanJsonAddStringToObject(obj, "k", "v"); + + char smallBuf[4] = {0}; + char *out = RyanJsonPrintPreallocated(obj, smallBuf, sizeof(smallBuf), RyanJsonFalse, NULL); + TEST_ASSERT_NULL_MESSAGE(out, "Preallocated buffer 过小应返回 NULL"); + + RyanJsonDelete(obj); +} + +static void testPrintPreallocatedExactFit(void) +{ + RyanJson_t nullJson = RyanJsonCreateNull(NULL); + char buf[5]; + memset(buf, 'X', sizeof(buf)); + + char *out = RyanJsonPrintPreallocated(nullJson, buf, sizeof(buf), RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(out, "Preallocated buffer 刚好够用应成功"); + TEST_ASSERT_EQUAL_STRING("null", out); + + RyanJsonDelete(nullJson); +} + +static void testPrintPreallocatedExactFitObjectString(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddStringToObject(obj, "k", "v")); + + uint32_t expectedLen = 0; + char *expected = RyanJsonPrint(obj, 0, RyanJsonFalse, &expectedLen); + TEST_ASSERT_NOT_NULL(expected); + + char exactBuf[16] = {0}; + TEST_ASSERT_TRUE_MESSAGE(expectedLen + 1U <= sizeof(exactBuf), "测试缓冲区长度不足"); + + char *out = RyanJsonPrintPreallocated(obj, exactBuf, expectedLen + 1U, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(out, "对象(string)预分配刚好够用应成功"); + TEST_ASSERT_EQUAL_STRING(expected, out); + + RyanJsonFree(expected); + RyanJsonDelete(obj); +} + +// 该用例覆盖预分配缓冲行为,后续可按场景继续拆分 +static void testPrintPreallocatedObjectIntHeadroom(void) +{ + RyanJson_t obj = createObjectWithIntMember("a", 1); + + // 先拿到基准输出长度 + uint32_t expectLen = 0; + char *expect = RyanJsonPrint(obj, 0, RyanJsonFalse, &expectLen); + if (NULL == expect) + { + RyanJsonDelete(obj); + TEST_FAIL_MESSAGE("基准打印失败,无法构造刚好够用缓冲区"); + return; + } + + // 对象中包含 int 时,PrintNumber 内部会做 INT32_MIN 级别的预留校验, + // 因此“仅够最终输出长度”的缓冲区会失败,这是当前实现的既定行为。 + char *exactBuf = (char *)malloc((size_t)expectLen + 1U); + if (NULL == exactBuf) + { + RyanJsonFree(expect); + RyanJsonDelete(obj); + TEST_FAIL_MESSAGE("malloc 失败"); + return; + } + + char *out = RyanJsonPrintPreallocated(obj, exactBuf, expectLen + 1U, RyanJsonFalse, NULL); + TEST_ASSERT_NULL_MESSAGE(out, "对象(int)预分配仅按最终长度应失败(需要额外预留空间)"); + + // 给足预留空间后应成功 + char headroomBuf[32] = {0}; + out = RyanJsonPrintPreallocated(obj, headroomBuf, sizeof(headroomBuf), RyanJsonFalse, NULL); + if (NULL == out) + { + RyanJsonFree(expect); + free(exactBuf); + RyanJsonDelete(obj); + TEST_FAIL_MESSAGE("对象(int)预分配带预留空间应成功"); + return; + } + + TEST_ASSERT_EQUAL_STRING(expect, out); + TEST_ASSERT_EQUAL_UINT32(expectLen, (uint32_t)strlen(out)); + + RyanJsonFree(expect); + free(exactBuf); + RyanJsonDelete(obj); +} + +static void testPrintPreallocatedWithStyleNullArgs(void) +{ + RyanJson_t obj = createObjectWithIntMember("a", 1); + + RyanJsonPrintStyle style = makeStyle(" ", "\n", 1, RyanJsonTrue); + char buf[64] = {0}; + + TEST_ASSERT_NULL(RyanJsonPrintPreallocatedWithStyle(NULL, buf, sizeof(buf), &style, NULL)); + TEST_ASSERT_NULL(RyanJsonPrintPreallocatedWithStyle(obj, NULL, sizeof(buf), &style, NULL)); + TEST_ASSERT_NULL(RyanJsonPrintPreallocatedWithStyle(obj, buf, sizeof(buf), NULL, NULL)); + TEST_ASSERT_NULL(RyanJsonPrintPreallocatedWithStyle(obj, buf, 0, &style, NULL)); + + RyanJsonDelete(obj); +} + +static void testPrintPreallocatedWithStyleTooSmall(void) +{ + RyanJson_t obj = createObjectWithIntMember("a", 1); + + RyanJsonPrintStyle style = makeStyle(" ", "\n", 1, RyanJsonTrue); + char tinyBuf[2] = {0}; + + char *out = RyanJsonPrintPreallocatedWithStyle(obj, tinyBuf, sizeof(tinyBuf), &style, NULL); + TEST_ASSERT_NULL_MESSAGE(out, "WithStyle 预分配缓冲区过小应返回 NULL"); + + RyanJsonDelete(obj); +} + +static void testPrintPreallocatedWithStyleSuccessAndLen(void) +{ + RyanJson_t obj = createObjectWithIntMember("a", 1); + + RyanJsonPrintStyle style = makeStyle(" ", "\n", 2, RyanJsonTrue); + char buf[64] = {0}; + uint32_t len = 0; + + char *out = RyanJsonPrintPreallocatedWithStyle(obj, buf, sizeof(buf), &style, &len); + TEST_ASSERT_NOT_NULL(out); + TEST_ASSERT_EQUAL_STRING("{\n \"a\": 1\n}", out); + TEST_ASSERT_EQUAL_UINT32((uint32_t)strlen(out), len); + + RyanJsonDelete(obj); +} + +static void testPrintPreallocatedWithStyleFormatFalseMinified(void) +{ + RyanJson_t obj = createObjectWithIntMember("a", 1); + + RyanJsonPrintStyle style = makeStyle("--------", "\r\n", 4, RyanJsonFalse); + char buf[64] = {0}; + + char *out = RyanJsonPrintPreallocatedWithStyle(obj, buf, sizeof(buf), &style, NULL); + TEST_ASSERT_NOT_NULL(out); + TEST_ASSERT_EQUAL_STRING("{\"a\":1}", out); + + RyanJsonDelete(obj); +} + +static void testPrintIntBoundaryPreallocated(void) +{ + RyanJson_t intJson = RyanJsonCreateInt(NULL, INT32_MIN); + TEST_ASSERT_NOT_NULL(intJson); + + char tooSmall[11] = {0}; + char *out = RyanJsonPrintPreallocated(intJson, tooSmall, sizeof(tooSmall), RyanJsonFalse, NULL); + TEST_ASSERT_NULL_MESSAGE(out, "INT32_MIN 预分配 11 字节应失败"); + + char exactFit[12] = {0}; + out = RyanJsonPrintPreallocated(intJson, exactFit, sizeof(exactFit), RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(out, "INT32_MIN 预分配 12 字节应成功"); + TEST_ASSERT_EQUAL_STRING("-2147483648", out); + + RyanJsonDelete(intJson); +} + +static void testPrintDoubleBoundaryPreallocated(void) +{ + RyanJson_t doubleJson = RyanJsonCreateDouble(NULL, 1.5); + TEST_ASSERT_NOT_NULL(doubleJson); + + uint32_t expectLen = 0; + char *expect = RyanJsonPrint(doubleJson, 0, RyanJsonFalse, &expectLen); + if (NULL == expect) + { + RyanJsonDelete(doubleJson); + TEST_FAIL_MESSAGE("double 基准打印失败"); + return; + } + + // 与 int 类似,double 路径会先申请固定工作区(RyanJsonDoubleBufferSize), + // 所以仅按最终输出长度预分配会失败。 + char *exactBuf = (char *)malloc((size_t)expectLen + 1U); + if (NULL == exactBuf) + { + RyanJsonFree(expect); + RyanJsonDelete(doubleJson); + TEST_FAIL_MESSAGE("malloc 失败"); + return; + } + + char *out = RyanJsonPrintPreallocated(doubleJson, exactBuf, expectLen + 1U, RyanJsonFalse, NULL); + TEST_ASSERT_NULL_MESSAGE(out, "double 预分配仅按最终长度应失败(需要额外预留空间)"); + + char headroomBuf[128] = {0}; + out = RyanJsonPrintPreallocated(doubleJson, headroomBuf, sizeof(headroomBuf), RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL_MESSAGE(out, "double 预分配带预留空间应成功"); + TEST_ASSERT_EQUAL_STRING(expect, out); + + RyanJsonFree(expect); + free(exactBuf); + RyanJsonDelete(doubleJson); + + // 特殊值:NaN / Infinity 应打印为 null + RyanJson_t nanJson = RyanJsonCreateDouble(NULL, NAN); + RyanJson_t infJson = RyanJsonCreateDouble(NULL, INFINITY); + TEST_ASSERT_NOT_NULL(nanJson); + TEST_ASSERT_NOT_NULL(infJson); + + char specialBuf[128] = {0}; + out = RyanJsonPrintPreallocated(nanJson, specialBuf, sizeof(specialBuf), RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL(out); + TEST_ASSERT_EQUAL_STRING("null", out); + + memset(specialBuf, 0, sizeof(specialBuf)); + out = RyanJsonPrintPreallocated(infJson, specialBuf, sizeof(specialBuf), RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL(out); + TEST_ASSERT_EQUAL_STRING("null", out); + + RyanJsonDelete(nanJson); + RyanJsonDelete(infJson); +} + +static void testPrintDoubleScientificAndRoundtrip(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddDoubleToObject(obj, "large", 1.0e20)); + TEST_ASSERT_TRUE(RyanJsonAddDoubleToObject(obj, "tiny", 1.0e-5)); + TEST_ASSERT_TRUE(RyanJsonAddDoubleToObject(obj, "frac", 0.1)); + + char *printed = RyanJsonPrint(obj, 128, RyanJsonFalse, NULL); + TEST_ASSERT_NOT_NULL(printed); + + // large 在当前测试配置下应走科学计数法 + char *largePos = strstr(printed, "\"large\":"); + char *tinyPos = strstr(printed, "\"tiny\":"); + TEST_ASSERT_NOT_NULL(largePos); + TEST_ASSERT_NOT_NULL(tinyPos); + + char *largeValueStart = strchr(largePos, ':'); + TEST_ASSERT_NOT_NULL(largeValueStart); + largeValueStart++; + + char *largeComma = strpbrk(largePos, ",}"); + char *tinyComma = strpbrk(tinyPos, ",}"); + TEST_ASSERT_NOT_NULL(largeComma); + TEST_ASSERT_NOT_NULL(tinyComma); + + int32_t largeHasScientific = (NULL != memchr((const void *)largeValueStart, 'e', (size_t)(largeComma - largeValueStart))) || + (NULL != memchr((const void *)largeValueStart, 'E', (size_t)(largeComma - largeValueStart))); + +#ifdef RyanJsonLinuxTestEnv + // Linux 测试分支会在实现内覆盖格式选择,large 路径固定走科学计数法 + TEST_ASSERT_TRUE_MESSAGE(largeHasScientific, "RyanJsonLinuxTestEnv 下 large 应包含科学计数法输出"); +#elif true == RyanJsonSnprintfSupportScientific + TEST_ASSERT_TRUE_MESSAGE(largeHasScientific, "开启科学计数法时 large 应包含科学计数法输出"); +#else + TEST_ASSERT_FALSE_MESSAGE(largeHasScientific, "关闭科学计数法时 large 不应包含科学计数法输出"); +#endif + + RyanJson_t roundtrip = RyanJsonParse(printed); + RyanJsonFree(printed); + TEST_ASSERT_NOT_NULL(roundtrip); + + TEST_ASSERT_TRUE(RyanJsonCompareDouble(1.0e20, RyanJsonGetDoubleValue(RyanJsonGetObjectToKey(roundtrip, "large")))); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(1.0e-5, RyanJsonGetDoubleValue(RyanJsonGetObjectToKey(roundtrip, "tiny")))); + TEST_ASSERT_TRUE(RyanJsonCompareDouble(0.1, RyanJsonGetDoubleValue(RyanJsonGetObjectToKey(roundtrip, "frac")))); + + RyanJsonDelete(roundtrip); + RyanJsonDelete(obj); +} + +static int32_t gPrintFailAfter = -1; +static int32_t gPrintAllocCount = 0; + +static void *printFailMalloc(size_t size) +{ + if (gPrintFailAfter >= 0 && gPrintAllocCount++ >= gPrintFailAfter) { return NULL; } + return unityTestMalloc(size); +} + +static void *printFailRealloc(void *block, size_t size) +{ + if (gPrintFailAfter >= 0 && gPrintAllocCount++ >= gPrintFailAfter) { return NULL; } + return unityTestRealloc(block, size); +} + +static void setPrintFailAfter(int32_t failAfter) +{ + gPrintFailAfter = failAfter; + gPrintAllocCount = 0; + RyanJsonInitHooks(printFailMalloc, unityTestFree, printFailRealloc); +} + +static void restorePrintHooks(void) +{ + RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc); + gPrintFailAfter = -1; + gPrintAllocCount = 0; +} + +static void testPrintOom(void) +{ + RyanJson_t obj = createObjectWithIntMember("a", 1); + + setPrintFailAfter(0); + + char *printed = RyanJsonPrint(obj, 32, RyanJsonFalse, NULL); + + // 恢复 hooks + restorePrintHooks(); + if (printed) { RyanJsonFree(printed); } + TEST_ASSERT_NULL_MESSAGE(printed, "Print OOM 应返回 NULL"); + + RyanJsonDelete(obj); +} + +static void testPrintFinalAppendOom(void) +{ + char longStr[58]; + memset(longStr, 'a', sizeof(longStr) - 1); + longStr[sizeof(longStr) - 1] = '\0'; + + RyanJson_t obj = RyanJsonCreateObject(); + RyanJsonAddStringToObject(obj, "k", longStr); + + setPrintFailAfter(1); + + char *printed = RyanJsonPrint(obj, RyanJsonPrintfPreAlloSize, RyanJsonFalse, NULL); + + // 恢复 hooks + restorePrintHooks(); + + if (printed) { RyanJsonFree(printed); } + TEST_ASSERT_NULL_MESSAGE(printed, "Print 末尾扩容失败应返回 NULL"); + + RyanJsonDelete(obj); +} + +/** + * @brief 定制化打印风格测试 + * 验证 RyanJsonPrintWithStyle 接口以及各种格式化选项 + */ +static void testPrintCrazy(void) +{ + // 打印空对象/空数组 + RyanJson_t emptyObj = RyanJsonCreateObject(); + char *s = RyanJsonPrint(emptyObj, 0, RyanJsonFalse, NULL); + TEST_ASSERT_EQUAL_STRING("{}", s); + RyanJsonFree(s); + RyanJsonDelete(emptyObj); + + RyanJson_t emptyArr = RyanJsonCreateArray(); + s = RyanJsonPrint(emptyArr, 0, RyanJsonFalse, NULL); + TEST_ASSERT_EQUAL_STRING("[]", s); + RyanJsonFree(s); + RyanJsonDelete(emptyArr); + + // 极限缓冲区测试 + RyanJson_t json = RyanJsonCreateObject(); + RyanJsonAddStringToObject(json, "k", "v"); + // size=0:自动选择初始缓冲 + s = RyanJsonPrint(json, 0, RyanJsonFalse, NULL); + TEST_ASSERT_EQUAL_STRING("{\"k\":\"v\"}", s); + RyanJsonFree(s); + + // size=1:初始空间过小,预期自动扩容 + s = RyanJsonPrint(json, 1, RyanJsonFalse, NULL); + TEST_ASSERT_EQUAL_STRING("{\"k\":\"v\"}", s); + RyanJsonFree(s); + RyanJsonDelete(json); + + // 极端打印风格 + RyanJson_t obj = createObjectWithIntMember("a", 1); + + RyanJsonPrintStyle crazyStyle = makeStyle("--------", "\n\n", 4, RyanJsonTrue); + uint32_t len; + s = RyanJsonPrintWithStyle(obj, 10, &crazyStyle, &len); + // 预期片段:{\n\n--------"a": 1\n\n} + // 校验长度与关键片段 + TEST_ASSERT_EQUAL_INT(strlen(s), len); + TEST_ASSERT_NOT_NULL(strstr(s, "--------\"a\": 1")); + RyanJsonFree(s); + RyanJsonDelete(obj); +} + +static void testPrintDefault(void) +{ + const char *jsonstr = "{\"a\":1,\"b\":true}"; + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL(json); + + // 测试默认格式化打印(使用 RyanJsonPrint 简化校验) + char *defaultFormat = RyanJsonPrint(json, 256, RyanJsonTrue, NULL); + // 期望形如: + // { + // \t"a": 1, + // \t"b": true + // } + TEST_ASSERT_NOT_NULL(defaultFormat); + TEST_ASSERT_TRUE_MESSAGE(strstr(defaultFormat, "\t\"a\": 1") != NULL, "默认格式化:缩进或冒号后空格错误"); + RyanJsonFree(defaultFormat); + RyanJsonDelete(json); +} + +static void testPrintCustomStyle(void) +{ + const char *jsonstr = "{\"a\":1,\"b\":true}"; + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL(json); + + // 测试自定义风格:2 个空格缩进、Windows 换行、冒号后 2 个空格 + RyanJsonPrintStyle style = makeStyle(" ", "\r\n", 2, RyanJsonTrue); + + uint32_t len = 0; + char *customPrint = RyanJsonPrintWithStyle(json, 256, &style, &len); + TEST_ASSERT_NOT_NULL(customPrint); + + // 校验特征点 + TEST_ASSERT_TRUE_MESSAGE(strstr(customPrint, "\r\n \"a\": 1") != NULL, "自定义格式化:缩进、换行或冒号后空格匹配失败"); + TEST_ASSERT_EQUAL_INT_MESSAGE(strlen(customPrint), len, "返回长度不一致"); + + RyanJsonFree(customPrint); + RyanJsonDelete(json); +} + +void testPrintRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testPrintStyleEdgeCases); + RUN_TEST(testPrintPreallocatedTooSmall); + RUN_TEST(testPrintPreallocatedExactFit); + RUN_TEST(testPrintPreallocatedExactFitObjectString); + RUN_TEST(testPrintPreallocatedObjectIntHeadroom); + RUN_TEST(testPrintPreallocatedWithStyleNullArgs); + RUN_TEST(testPrintPreallocatedWithStyleTooSmall); + RUN_TEST(testPrintPreallocatedWithStyleSuccessAndLen); + RUN_TEST(testPrintPreallocatedWithStyleFormatFalseMinified); + RUN_TEST(testPrintIntBoundaryPreallocated); + RUN_TEST(testPrintDoubleBoundaryPreallocated); + RUN_TEST(testPrintDoubleScientificAndRoundtrip); + RUN_TEST(testPrintOom); + RUN_TEST(testPrintFinalAppendOom); + RUN_TEST(testPrintCrazy); + RUN_TEST(testPrintDefault); + RUN_TEST(testPrintCustomStyle); +} diff --git a/test/unityTest/cases/utils/testRobust.c b/test/unityTest/cases/utils/testRobust.c new file mode 100644 index 0000000..920e6db --- /dev/null +++ b/test/unityTest/cases/utils/testRobust.c @@ -0,0 +1,525 @@ +#include "testBase.h" + +static int32_t gFailAfter = -1; +static int32_t gAllocCount = 0; + +static void *failingMalloc(size_t size) +{ + if (gFailAfter >= 0 && gAllocCount++ >= gFailAfter) { return NULL; } + return unityTestMalloc(size); +} + +static void *failingRealloc(void *block, size_t size) +{ + if (gFailAfter >= 0 && gAllocCount++ >= gFailAfter) { return NULL; } + return unityTestRealloc(block, size); +} + +static void setFailAfter(int32_t n) +{ + gFailAfter = n; + gAllocCount = 0; + RyanJsonInitHooks(failingMalloc, unityTestFree, failingRealloc); +} + +static void setFailAfterNoRealloc(int32_t n) +{ + gFailAfter = n; + gAllocCount = 0; + RyanJsonInitHooks(failingMalloc, unityTestFree, NULL); +} + +static void restoreHooks(void) +{ + RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc); + gFailAfter = -1; + gAllocCount = 0; +} + +static void assertMinifyEq(char *buffer, int32_t textLen, const char *expected) +{ + uint32_t len = RyanJsonMinify(buffer, textLen); + TEST_ASSERT_EQUAL_STRING(expected, buffer); + TEST_ASSERT_EQUAL_UINT32((uint32_t)strlen(expected), len); +} + +#define expectCreateNullUnderOom(failAfter, expr, msg) \ + do \ + { \ + setFailAfter((failAfter)); \ + RyanJson_t _node = (expr); \ + restoreHooks(); \ + if (_node) { RyanJsonDelete(_node); } \ + TEST_ASSERT_NULL_MESSAGE(_node, (msg)); \ + } while (0) + +static void testParseOptionsTerminator(void) +{ + const char *end = NULL; + + // 允许尾部内容:requireNullTerminator = false + const char *text = " {\"a\":1} trailing"; + RyanJson_t json = RyanJsonParseOptions(text, (uint32_t)strlen(text), RyanJsonFalse, &end); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "ParseOptions(允许尾部) 失败"); + TEST_ASSERT_NOT_NULL_MESSAGE(end, "parseEndPtr 不应为 NULL"); + TEST_ASSERT_EQUAL_STRING_MESSAGE(" trailing", end, "parseEndPtr 位置错误"); + RyanJsonDelete(json); + + // 禁止尾部内容:requireNullTerminator = true + json = RyanJsonParseOptions(text, (uint32_t)strlen(text), RyanJsonTrue, NULL); + TEST_ASSERT_NULL_MESSAGE(json, "ParseOptions(强制结尾) 应失败"); + + // 仅包含空白尾部:应成功,parseEndPtr 应指向末尾 + text = "{\"a\":1} \t\r\n"; + json = RyanJsonParseOptions(text, (uint32_t)strlen(text), RyanJsonTrue, &end); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "ParseOptions(空白尾部) 失败"); + TEST_ASSERT_NOT_NULL(end); + TEST_ASSERT_EQUAL_CHAR('\0', *end); + RyanJsonDelete(json); + + // 限长解析:仅解析前半段 + const char *concat = "{\"a\":1}{\"b\":2}"; + uint32_t firstLen = (uint32_t)strlen("{\"a\":1}"); + end = NULL; + json = RyanJsonParseOptions(concat, firstLen, RyanJsonTrue, &end); + TEST_ASSERT_NOT_NULL_MESSAGE(json, "ParseOptions(限长解析) 失败"); + TEST_ASSERT_NOT_NULL(end); + TEST_ASSERT_EQUAL_STRING_MESSAGE("{\"b\":2}", end, "size-limited parseEndPtr 错误"); + RyanJsonDelete(json); +} + +static void testMinifyComplexEscapesAndComments(void) +{ + // 注释应被剔除,但字符串里的注释片段必须保留 + char buf[] = " { \"url\" : \"http://x//y\" , /* block */ \"path\" : \"C:\\\\tmp\\\\/*file*/\" , // line\n \"ok\" : true } "; + assertMinifyEq(buf, (int32_t)strlen(buf), "{\"url\":\"http://x//y\",\"path\":\"C:\\\\tmp\\\\/*file*/\",\"ok\":true}"); + + // 转义引号与字符串内 // 也应保持原样 + char buf2[] = "{\"msg\":\"he said: \\\"/*no*/\\\" //keep\" , \"v\" : 1} "; + assertMinifyEq(buf2, (int32_t)strlen(buf2), "{\"msg\":\"he said: \\\"/*no*/\\\" //keep\",\"v\":1}"); +} + +static void testMinifyNoTerminatorOverflow(void) +{ + // textLen 不包含额外 '\0' 空间时,不应越界写终止符 + uint8_t rawBuf[8] = {'{', '\"', 'a', '\"', ':', '1', '}', '#'}; + uint32_t len = RyanJsonMinify((char *)rawBuf, 7); + TEST_ASSERT_EQUAL_UINT32(7, len); + TEST_ASSERT_EQUAL_UINT8('#', rawBuf[7]); + TEST_ASSERT_EQUAL_UINT8('{', rawBuf[0]); + TEST_ASSERT_EQUAL_UINT8('\"', rawBuf[1]); + TEST_ASSERT_EQUAL_UINT8('a', rawBuf[2]); + TEST_ASSERT_EQUAL_UINT8('\"', rawBuf[3]); + TEST_ASSERT_EQUAL_UINT8(':', rawBuf[4]); + TEST_ASSERT_EQUAL_UINT8('1', rawBuf[5]); + TEST_ASSERT_EQUAL_UINT8('}', rawBuf[6]); +} + +static void testMinifyCommentBoundaryCases(void) +{ + // 行注释直到输入结束(无换行) + char lineTail[] = "{\"a\":1}//tail"; + assertMinifyEq(lineTail, (int32_t)strlen(lineTail), "{\"a\":1}"); + + // 块注释未闭合:应安全走到 end,不越界 + char blockTail[] = "{\"a\":1}/*tail"; + assertMinifyEq(blockTail, (int32_t)strlen(blockTail), "{\"a\":1}"); + + // 末尾孤立 '/' 不是注释起始,应保留 + char loneSlash[] = "{\"a\":1}/"; + assertMinifyEq(loneSlash, (int32_t)strlen(loneSlash), "{\"a\":1}/"); + + // 字符串内末尾反斜杠且 textLen 截断,覆盖 text + 1 < end 为 false 的路径 + char rawTruncated[3] = {'\"', 'a', '\\'}; + uint32_t len = RyanJsonMinify(rawTruncated, 3); + TEST_ASSERT_EQUAL_UINT32(3U, len); + TEST_ASSERT_EQUAL_UINT8('\"', (uint8_t)rawTruncated[0]); + TEST_ASSERT_EQUAL_UINT8('a', (uint8_t)rawTruncated[1]); + TEST_ASSERT_EQUAL_UINT8('\\', (uint8_t)rawTruncated[2]); +} + +static void testMinifyWhitespaceAndNonCommentSlashPaths(void) +{ + char tabLeading[] = "\t{\"a\":1}"; + assertMinifyEq(tabLeading, (int32_t)strlen(tabLeading), "{\"a\":1}"); + + char crLeading[] = "\r{\"b\":2}"; + assertMinifyEq(crLeading, (int32_t)strlen(crLeading), "{\"b\":2}"); + + // "/x" 不是注释,应完整保留 + char slashNonComment[] = "/x"; + assertMinifyEq(slashNonComment, (int32_t)strlen(slashNonComment), "/x"); + + // 块注释内出现 '*' 但后续不是 '/',应继续扫描直到真正闭合 + char blockWithStar[] = "/*a*b*/{\"k\":1}"; + assertMinifyEq(blockWithStar, (int32_t)strlen(blockWithStar), "{\"k\":1}"); + + // 未闭合块注释且最后一个字符是 '*' + char blockEndWithStar[] = "/*abc*"; + assertMinifyEq(blockEndWithStar, (int32_t)strlen(blockEndWithStar), ""); +} + +static void testParseAllocatedKeyCleanupOnValueError(void) +{ + const char *bad = "{\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\":}"; + RyanJson_t json = RyanJsonParse(bad); + TEST_ASSERT_NULL_MESSAGE(json, "长 key 后 value 非法时应解析失败"); +} + +static void testPrintPreallocatedArgGuards(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + RyanJsonAddStringToObject(obj, "k", "v"); + + char buf[16] = {0}; + TEST_ASSERT_NULL(RyanJsonPrintPreallocated(NULL, buf, sizeof(buf), RyanJsonFalse, NULL)); + TEST_ASSERT_NULL(RyanJsonPrintPreallocated(obj, NULL, sizeof(buf), RyanJsonFalse, NULL)); + TEST_ASSERT_NULL(RyanJsonPrintPreallocated(obj, buf, 0, RyanJsonFalse, NULL)); + + uint32_t len = 0; + char *out = RyanJsonPrintPreallocated(obj, buf, sizeof(buf), RyanJsonFalse, &len); + TEST_ASSERT_NOT_NULL(out); + TEST_ASSERT_EQUAL_STRING("{\"k\":\"v\"}", out); + TEST_ASSERT_EQUAL_UINT32((uint32_t)strlen(out), len); + + RyanJsonDelete(obj); +} + +static void testInitHooksAndCreateApiGuards(void) +{ + TEST_ASSERT_FALSE_MESSAGE(RyanJsonInitHooks(NULL, unityTestFree, unityTestRealloc), "InitHooks(NULL malloc) 应失败"); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonInitHooks(unityTestMalloc, NULL, unityTestRealloc), "InitHooks(NULL free) 应失败"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc), "恢复默认 hooks 应成功"); + + TEST_ASSERT_NULL_MESSAGE(RyanJsonCreateString("k", NULL), "CreateString(NULL value) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonCreateIntArray(NULL, 1), "CreateIntArray(NULL,1) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonCreateDoubleArray(NULL, 1), "CreateDoubleArray(NULL,1) 应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonCreateStringArray(NULL, 1), "CreateStringArray(NULL,1) 应返回 NULL"); +} + +static void testNumberBoundaries(void) +{ + RyanJson_t json = RyanJsonParse("{\"i\":2147483647,\"i2\":-2147483648,\"i3\":2147483648,\"n\":-0}"); + TEST_ASSERT_NOT_NULL(json); + + RyanJson_t i = RyanJsonGetObjectToKey(json, "i"); + RyanJson_t i2 = RyanJsonGetObjectToKey(json, "i2"); + RyanJson_t i3 = RyanJsonGetObjectToKey(json, "i3"); + RyanJson_t n = RyanJsonGetObjectToKey(json, "n"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(i), "2147483647 应解析为 int32_t"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(i2), "-2147483648 应解析为 int32_t"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(i3), "2147483648 应解析为 double"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(n), "-0 应解析为 int32_t"); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, RyanJsonGetIntValue(n), "-0 值错误"); + + RyanJsonDelete(json); +} + +static void testVarargsPathTypeMismatchAndNullInput(void) +{ + RyanJson_t root = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(root); + + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 7)); + RyanJsonAddItemToObject(root, "arr", arr); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(root, "n", 42)); + + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(NULL, "a"), "NULL 根节点应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToIndex(NULL, 0), "NULL 根节点应返回 NULL"); + + // 根节点是 object,也支持按索引获取直接子节点 + TEST_ASSERT_NOT_NULL_MESSAGE(RyanJsonGetObjectToIndex(root, 0), "object 根节点按 index=0 获取应成功"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToIndex(root, 100), "object 根节点 index 越界应返回 NULL"); + + // 标量节点继续向下取 key/index 都应失败 + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(root, "n", "x"), "标量节点不应继续按 key 深入"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToIndex(arr, 0, 0), "标量节点不应继续按 index 深入"); + + TEST_ASSERT_FALSE(RyanJsonHasObjectToKey(root, "n", "x")); + TEST_ASSERT_FALSE(RyanJsonHasObjectToIndex(arr, 0, 0)); + + // 首层查找失败路径(不进入可变参数迭代) + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectByKeys(root, "missing", NULL), "首层 key 缺失应返回 NULL"); + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectByIndexs(arr, 1, UINT32_MAX), "首层 index 越界应返回 NULL"); + + RyanJsonDelete(root); +} + +static void testDuplicateKeyDetach(void) +{ + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "dup", 1)); +#if true == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_FALSE_MESSAGE(RyanJsonAddIntToObject(obj, "dup", 2), "严格模式下对象不应允许重复 key"); +#else + TEST_ASSERT_TRUE_MESSAGE(RyanJsonAddIntToObject(obj, "dup", 2), "非严格模式下对象应允许重复 key"); +#endif + + RyanJson_t only = RyanJsonGetObjectByKey(obj, "dup"); + TEST_ASSERT_NOT_NULL(only); +#if true == RyanJsonDefaultAddAtHead && false == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(only)); +#else + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(only)); +#endif + + RyanJson_t detached = RyanJsonDetachByKey(obj, "dup"); + TEST_ASSERT_NOT_NULL(detached); +#if true == RyanJsonDefaultAddAtHead && false == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(detached)); +#else + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(detached)); +#endif + RyanJsonDelete(detached); + +#if true == RyanJsonStrictObjectKeyCheck + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(obj, "dup")); +#else + RyanJson_t second = RyanJsonGetObjectByKey(obj, "dup"); + TEST_ASSERT_NOT_NULL(second); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(second)); +#else + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(second)); +#endif + RyanJson_t detached2 = RyanJsonDetachByKey(obj, "dup"); + TEST_ASSERT_NOT_NULL(detached2); +#if true == RyanJsonDefaultAddAtHead + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetIntValue(detached2)); +#else + TEST_ASSERT_EQUAL_INT(2, RyanJsonGetIntValue(detached2)); +#endif + RyanJsonDelete(detached2); + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(obj, "dup")); +#endif + + RyanJsonDelete(obj); +} + +static void testInsertOutOfRangeAndKeyValidation(void) +{ + // Array:index 超出范围应追加到尾部 + RyanJson_t arr = RyanJsonCreateArray(); + RyanJsonAddIntToArray(arr, 1); + RyanJsonAddIntToArray(arr, 2); + TEST_ASSERT_TRUE(RyanJsonInsert(arr, 100, RyanJsonCreateInt(NULL, 3))); + TEST_ASSERT_EQUAL_INT(3, RyanJsonGetSize(arr)); + TEST_ASSERT_EQUAL_INT(3, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(arr, 2))); + RyanJsonDelete(arr); + + // Object:item 无 key 应失败 + RyanJson_t obj = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj, "a", 1); + RyanJson_t noKey = RyanJsonCreateInt(NULL, 2); + TEST_ASSERT_FALSE_MESSAGE(RyanJsonInsert(obj, 0, noKey), "Object 插入无 key item 应失败"); + TEST_ASSERT_EQUAL_INT(1, RyanJsonGetSize(obj)); + RyanJsonDelete(obj); +} + +static void testGetSizeNullAndContainer(void) +{ + TEST_ASSERT_EQUAL_UINT32_MESSAGE(0, RyanJsonGetSize(NULL), "NULL GetSize 应返回 0"); + + RyanJson_t num = RyanJsonParse("1"); + TEST_ASSERT_NOT_NULL(num); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, RyanJsonGetSize(num), "标量 GetSize 应返回 1"); + RyanJsonDelete(num); + + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "a", 1)); + TEST_ASSERT_TRUE(RyanJsonAddItemToObject(obj, "b", RyanJsonCreateObject())); + TEST_ASSERT_EQUAL_UINT32_MESSAGE(2, RyanJsonGetSize(obj), "对象 GetSize 应返回直接子节点数量"); + TEST_ASSERT_EQUAL_UINT32_MESSAGE(0, RyanJsonGetSize(RyanJsonGetObjectToKey(obj, "b")), "空对象 GetSize 应为 0"); + RyanJsonDelete(obj); + + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 1)); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 2)); + TEST_ASSERT_EQUAL_UINT32_MESSAGE(2, RyanJsonGetSize(arr), "数组 GetSize 应返回元素数量"); + RyanJsonDelete(arr); +} + +static void testInternalUtilsBranchCoverage(void) +{ + const char *same = "same"; + TEST_ASSERT_TRUE(RyanJsonInternalStrEq(same, same)); + TEST_ASSERT_FALSE(RyanJsonInternalStrEq("abc", "abd")); + + TEST_ASSERT_EQUAL_UINT8(4, RyanJsonInternalDecodeKeyLenField(3)); + TEST_ASSERT_EQUAL_UINT8(2, RyanJsonInternalDecodeKeyLenField(2)); + TEST_ASSERT_EQUAL_UINT8(1, RyanJsonInternalCalcLenBytes(UINT8_MAX)); + TEST_ASSERT_EQUAL_UINT8(2, RyanJsonInternalCalcLenBytes((uint32_t)UINT8_MAX + 1U)); + TEST_ASSERT_EQUAL_UINT8(3, RyanJsonInternalCalcLenBytes((uint32_t)UINT16_MAX + 1U)); + + RyanJson_t arr = RyanJsonCreateArray(); + TEST_ASSERT_NOT_NULL(arr); + TEST_ASSERT_TRUE(RyanJsonAddIntToArray(arr, 7)); + RyanJson_t v = RyanJsonGetObjectByIndexs(arr, 0, UINT32_MAX); + TEST_ASSERT_NOT_NULL(v); + TEST_ASSERT_EQUAL_INT(7, RyanJsonGetIntValue(v)); + RyanJsonDelete(arr); +} + +static void testTypedArrayCreationZeroCount(void) +{ + int32_t intOne[1] = {1}; + double doubleOne[1] = {1.5}; + const char *strOne[1] = {"x"}; + + RyanJson_t iArr = RyanJsonCreateIntArray(intOne, 0); + RyanJson_t dArr = RyanJsonCreateDoubleArray(doubleOne, 0); + RyanJson_t sArr = RyanJsonCreateStringArray(strOne, 0); + + TEST_ASSERT_NOT_NULL(iArr); + TEST_ASSERT_NOT_NULL(dArr); + TEST_ASSERT_NOT_NULL(sArr); + TEST_ASSERT_EQUAL_UINT32(0, RyanJsonGetSize(iArr)); + TEST_ASSERT_EQUAL_UINT32(0, RyanJsonGetSize(dArr)); + TEST_ASSERT_EQUAL_UINT32(0, RyanJsonGetSize(sArr)); + + RyanJsonDelete(iArr); + RyanJsonDelete(dArr); + RyanJsonDelete(sArr); +} + +static void testTypedArrayCreationOomPaths(void) +{ + int32_t ints[2] = {1, 2}; + double doubles[2] = {1.1, 2.2}; + const char *strs[2] = {"a", "b"}; + + expectCreateNullUnderOom(1, RyanJsonCreateIntArray(ints, 2), "CreateIntArray OOM 路径应返回 NULL"); + expectCreateNullUnderOom(1, RyanJsonCreateDoubleArray(doubles, 2), "CreateDoubleArray OOM 路径应返回 NULL"); + expectCreateNullUnderOom(1, RyanJsonCreateStringArray(strs, 2), "CreateStringArray OOM 路径应返回 NULL"); +} + +static void testCreateScalarOomAndTypeGuards(void) +{ + // CreateInt/CreateDouble 失败分支 + expectCreateNullUnderOom(0, RyanJsonCreateInt(NULL, 1), "CreateInt OOM 应返回 NULL"); + expectCreateNullUnderOom(0, RyanJsonCreateDouble(NULL, 1.5), "CreateDouble OOM 应返回 NULL"); + + // 类型判定与 GetObjectByKey 空参分支 + RyanJson_t nullNode = RyanJsonCreateNull(NULL); + RyanJson_t intNode = RyanJsonCreateInt(NULL, 7); + TEST_ASSERT_NOT_NULL(nullNode); + TEST_ASSERT_NOT_NULL(intNode); + TEST_ASSERT_TRUE(RyanJsonIsNull(nullNode)); + TEST_ASSERT_FALSE(RyanJsonIsNull(intNode)); + TEST_ASSERT_FALSE(RyanJsonIsNull(NULL)); + + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(obj, "a", 1)); + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(obj, NULL)); + TEST_ASSERT_NULL(RyanJsonGetObjectByKey(NULL, "a")); + + RyanJsonDelete(obj); + RyanJsonDelete(nullNode); + RyanJsonDelete(intNode); +} + +static void testPrintExpandFallbackWithoutRealloc(void) +{ + char longValue[600] = {0}; + memset(longValue, 'x', sizeof(longValue) - 1U); + + RyanJson_t obj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(obj); + TEST_ASSERT_TRUE(RyanJsonAddStringToObject(obj, "k", longValue)); + + // PrintWithStyle 至少会分配 RyanJsonPrintfPreAlloSize;长字符串可稳定触发扩容 + // 先成功分配初始缓冲,再在扩容时失败,覆盖 jsonRealloc=NULL 的 fallback 路径 + setFailAfterNoRealloc(1); + char *printed = RyanJsonPrint(obj, RyanJsonPrintfPreAlloSize, RyanJsonFalse, NULL); + restoreHooks(); + + if (printed) { RyanJsonFree(printed); } + TEST_ASSERT_NULL_MESSAGE(printed, "Print 扩容 fallback(malloc) 失败应返回 NULL"); + RyanJsonDelete(obj); +} + +static void testDuplicateEmptyContainerAndOomPaths(void) +{ + RyanJson_t root = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(root); + RyanJson_t emptyObj = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(emptyObj); + TEST_ASSERT_TRUE(RyanJsonAddItemToObject(root, "e", emptyObj)); + + RyanJson_t dup = RyanJsonDuplicate(root); + TEST_ASSERT_NOT_NULL_MESSAGE(dup, "Duplicate(含空容器) 应成功"); + RyanJson_t dupE = RyanJsonGetObjectToKey(dup, "e"); + TEST_ASSERT_NOT_NULL(dupE); + TEST_ASSERT_TRUE(RyanJsonIsObject(dupE)); + TEST_ASSERT_EQUAL_UINT32(0U, RyanJsonGetSize(dupE)); + RyanJsonDelete(dup); + RyanJsonDelete(root); + + root = RyanJsonCreateObject(); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(RyanJsonAddIntToObject(root, "a", 1)); + // 根节点复制成功,首个子节点复制失败,覆盖 Duplicate error__ 路径 + expectCreateNullUnderOom(1, RyanJsonDuplicate(root), "Duplicate OOM 路径应返回 NULL"); + RyanJsonDelete(root); +} + +static void testOomCreateParsePrint(void) +{ + // 创建对象:首次分配失败 + setFailAfter(0); + RyanJson_t obj = RyanJsonCreateObject(); + restoreHooks(); + if (obj) { RyanJsonDelete(obj); } + TEST_ASSERT_NULL_MESSAGE(obj, "CreateObject OOM 应返回 NULL"); + + // 解析流程:中途分配失败(长 key 触发 key buffer 分配) + const char *longKeyJson = + "{\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\":1}"; + setFailAfter(1); // root 成功,key buffer 失败 + RyanJson_t json = RyanJsonParse(longKeyJson); + restoreHooks(); + if (json) { RyanJsonDelete(json); } + TEST_ASSERT_NULL_MESSAGE(json, "Parse OOM 应返回 NULL"); + + // 打印流程:分配打印缓冲失败 + obj = RyanJsonCreateObject(); + RyanJsonAddIntToObject(obj, "a", 1); + setFailAfter(0); + char *printed = RyanJsonPrint(obj, 32, RyanJsonFalse, NULL); + restoreHooks(); + TEST_ASSERT_NULL_MESSAGE(printed, "Print OOM 应返回 NULL"); + RyanJsonDelete(obj); +} + +void testRobustRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testParseOptionsTerminator); + RUN_TEST(testMinifyComplexEscapesAndComments); + RUN_TEST(testMinifyNoTerminatorOverflow); + RUN_TEST(testMinifyCommentBoundaryCases); + RUN_TEST(testMinifyWhitespaceAndNonCommentSlashPaths); + RUN_TEST(testParseAllocatedKeyCleanupOnValueError); + RUN_TEST(testPrintPreallocatedArgGuards); + RUN_TEST(testInitHooksAndCreateApiGuards); + RUN_TEST(testNumberBoundaries); + RUN_TEST(testVarargsPathTypeMismatchAndNullInput); + RUN_TEST(testDuplicateKeyDetach); + RUN_TEST(testInsertOutOfRangeAndKeyValidation); + RUN_TEST(testGetSizeNullAndContainer); + RUN_TEST(testInternalUtilsBranchCoverage); + RUN_TEST(testTypedArrayCreationZeroCount); + RUN_TEST(testTypedArrayCreationOomPaths); + RUN_TEST(testCreateScalarOomAndTypeGuards); + RUN_TEST(testPrintExpandFallbackWithoutRealloc); + RUN_TEST(testDuplicateEmptyContainerAndOomPaths); + RUN_TEST(testOomCreateParsePrint); +} diff --git a/test/unityTest/cases/utils/testUtils.c b/test/unityTest/cases/utils/testUtils.c new file mode 100644 index 0000000..accebe4 --- /dev/null +++ b/test/unityTest/cases/utils/testUtils.c @@ -0,0 +1,257 @@ + +#include "testBase.h" +/** + * @brief Json 公共校验辅助函数 + */ + +void printJsonDebug(RyanJson_t json) +{ + char *str = RyanJsonPrint(json, 1024, RyanJsonTrue, NULL); + testLog("aa %s\r\n", str); + RyanJsonFree(str); +} + +void rootNodeCheckTest(RyanJson_t json) +{ + TEST_ASSERT_NOT_NULL_MESSAGE(json, "根节点检查:输入 Json 为空"); + + // 校验整数字段 + RyanJson_t inter = RyanJsonGetObjectToKey(json, "inter"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(inter), "字段 'inter' 不是整数类型"); + TEST_ASSERT_EQUAL_INT_MESSAGE(16, RyanJsonGetIntValue(inter), "字段 'inter' 值不正确"); + + // 校验浮点数字段 + RyanJson_t dbl = RyanJsonGetObjectToKey(json, "double"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(dbl), "字段 'double' 不是浮点数类型"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareDouble(RyanJsonGetDoubleValue(dbl), 16.89), "字段 'double' 值不正确"); + + // 校验字符串字段 + RyanJson_t str = RyanJsonGetObjectToKey(json, "string"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(str), "字段 'string' 不是字符串类型"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("hello", RyanJsonGetStringValue(str), "字段 'string' 值不正确"); + + // 校验布尔字段(true) + RyanJson_t bTrue = RyanJsonGetObjectToKey(json, "boolTrue"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(bTrue), "字段 'boolTrue' 不是布尔类型"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonTrue, RyanJsonGetBoolValue(bTrue), "字段 'boolTrue' 值不正确"); + + // 校验布尔字段(false) + RyanJson_t bFalse = RyanJsonGetObjectToKey(json, "boolFalse"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(bFalse), "字段 'boolFalse' 不是布尔类型"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonFalse, RyanJsonGetBoolValue(bFalse), "字段 'boolFalse' 值不正确"); + + // 校验 null 字段 + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsNull(RyanJsonGetObjectToKey(json, "null")), "字段 'null' 不是 Null 类型"); +} + +void itemNodeCheckTest(RyanJson_t json) +{ + RyanJson_t item = RyanJsonGetObjectToKey(json, "item"); + TEST_ASSERT_NOT_NULL_MESSAGE(item, "字段 'item' 不存在"); + rootNodeCheckTest(item); +} + +void arrayNodeCheckTest(RyanJson_t json, RyanJsonBool_e isReversed) +{ + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsArray(RyanJsonGetObjectToKey(json, "arrayInt")), "arrayInt 不是数组"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsArray(RyanJsonGetObjectToKey(json, "arrayDouble")), "arrayDouble 不是数组"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsArray(RyanJsonGetObjectToKey(json, "arrayString")), "arrayString 不是数组"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsArray(RyanJsonGetObjectToKey(json, "array")), "array 不是数组"); + + // 校验混合数组 array:[16, 16.89, "hello", true, false, null] + RyanJson_t array = RyanJsonGetObjectToKey(json, "array"); + TEST_ASSERT_EQUAL_INT_MESSAGE(6, RyanJsonGetSize(array), "混合数组长度不正确"); + + if (isReversed) + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsNull(RyanJsonGetObjectByIndex(array, 0)), "混合数组[0]不是 Null"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectByIndex(array, 1)), "混合数组[1]不是布尔值"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonFalse, RyanJsonGetBoolValue(RyanJsonGetObjectByIndex(array, 1)), "混合数组[1]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectByIndex(array, 2)), "混合数组[2]不是布尔值"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonTrue, RyanJsonGetBoolValue(RyanJsonGetObjectByIndex(array, 2)), "混合数组[2]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(RyanJsonGetObjectByIndex(array, 3)), "混合数组[3]不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("hello", RyanJsonGetStringValue(RyanJsonGetObjectByIndex(array, 3)), "混合数组[3]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(RyanJsonGetObjectByIndex(array, 4)), "混合数组[4]不是浮点数"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareDouble(RyanJsonGetDoubleValue(RyanJsonGetObjectByIndex(array, 4)), 16.89), + "混合数组[4]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(RyanJsonGetObjectByIndex(array, 5)), "混合数组[5]不是整数"); + TEST_ASSERT_EQUAL_INT_MESSAGE(16, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(array, 5)), "混合数组[5]值错误"); + } + else + { + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(RyanJsonGetObjectByIndex(array, 0)), "混合数组[0]不是整数"); + TEST_ASSERT_EQUAL_INT_MESSAGE(16, RyanJsonGetIntValue(RyanJsonGetObjectByIndex(array, 0)), "混合数组[0]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(RyanJsonGetObjectByIndex(array, 1)), "混合数组[1]不是浮点数"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareDouble(RyanJsonGetDoubleValue(RyanJsonGetObjectByIndex(array, 1)), 16.89), + "混合数组[1]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(RyanJsonGetObjectByIndex(array, 2)), "混合数组[2]不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("hello", RyanJsonGetStringValue(RyanJsonGetObjectByIndex(array, 2)), "混合数组[2]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectByIndex(array, 3)), "混合数组[3]不是布尔值"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonTrue, RyanJsonGetBoolValue(RyanJsonGetObjectByIndex(array, 3)), "混合数组[3]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsBool(RyanJsonGetObjectByIndex(array, 4)), "混合数组[4]不是布尔值"); + TEST_ASSERT_EQUAL_INT_MESSAGE(RyanJsonFalse, RyanJsonGetBoolValue(RyanJsonGetObjectByIndex(array, 4)), "混合数组[4]值错误"); + + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsNull(RyanJsonGetObjectByIndex(array, 5)), "混合数组[5]不是 Null"); + } + + // 校验强类型数组 + RyanJson_t arrayInt = RyanJsonGetObjectToKey(json, "arrayInt"); + for (int32_t i = 0; i < RyanJsonGetSize(arrayInt); i++) + { + RyanJson_t item = RyanJsonGetObjectByIndex(arrayInt, i); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsInt(item), "arrayInt 元素不是整数"); + TEST_ASSERT_EQUAL_INT_MESSAGE(16, RyanJsonGetIntValue(item), "arrayInt 元素值错误"); + } + + RyanJson_t arrayDouble = RyanJsonGetObjectToKey(json, "arrayDouble"); + for (int32_t i = 0; i < RyanJsonGetSize(arrayDouble); i++) + { + RyanJson_t item = RyanJsonGetObjectByIndex(arrayDouble, i); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsDouble(item), "arrayDouble 元素不是浮点数"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonCompareDouble(RyanJsonGetDoubleValue(item), 16.89), "arrayDouble 元素值错误"); + } + + RyanJson_t arrayString = RyanJsonGetObjectToKey(json, "arrayString"); + for (int32_t i = 0; i < RyanJsonGetSize(arrayString); i++) + { + RyanJson_t item = RyanJsonGetObjectByIndex(arrayString, i); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsString(item), "arrayString 元素不是字符串"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("hello", RyanJsonGetStringValue(item), "arrayString 元素值错误"); + } +} + +void arrayItemNodeCheckTest(RyanJson_t json) +{ + RyanJson_t arrayItem = RyanJsonGetObjectToKey(json, "arrayItem"); + TEST_ASSERT_TRUE_MESSAGE(RyanJsonIsArray(arrayItem), "arrayItem 不是数组"); + TEST_ASSERT_EQUAL_INT_MESSAGE(2, RyanJsonGetSize(arrayItem), "arrayItem 长度错误"); + + rootNodeCheckTest(RyanJsonGetObjectToIndex(arrayItem, 0)); + rootNodeCheckTest(RyanJsonGetObjectToIndex(arrayItem, 1)); +} + +void testCheckRootEx(RyanJson_t pJson, RyanJsonBool_e isReversed) +{ + rootNodeCheckTest(pJson); + itemNodeCheckTest(pJson); + arrayNodeCheckTest(pJson, isReversed); + arrayItemNodeCheckTest(pJson); +} + +void testCheckRoot(RyanJson_t pJson) +{ + testCheckRootEx(pJson, RyanJsonFalse); +} + +static void testUtilsBasic(void) +{ + char jsonstr[] = "{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true,\"boolFalse\":false,\"null\":null,\"item\":" + "{\"inter\":16,\"double\":16." + "89,\"string\":\"hello\"," + "\"boolTrue\":true,\"boolFalse\":false,\"null\":null},\"arrayInt\":[16,16,16,16,16],\"arrayDouble\":[16.89,16.89," + "16.89,16.89,16.89]," + "\"arrayString\":[\"hello\",\"hello\"," + "\"hello\",\"hello\",\"hello\"],\"array\":[16,16.89,\"hello\",true,false,null],\"arrayItem\":[{\"inter\":16," + "\"double\":16.89,\"string\":" + "\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null},{\"inter\":16,\"double\":16.89,\"string\":\"hello\",\"boolTrue\":true," + "\"boolFalse\":false,\"null\":null}]}"; + + RyanJson_t json = RyanJsonParse(jsonstr); + TEST_ASSERT_NOT_NULL(json); + + // 调用全部校验辅助函数 + testCheckRoot(json); + + RyanJsonDelete(json); +} + +static void testUtilsMinifyNoExtraSpace(void) +{ + char buf[4]; + buf[0] = 'a'; + buf[1] = 'b'; + buf[2] = 'c'; + buf[3] = 'X'; // 哨兵:不应被覆盖 + + uint32_t count = RyanJsonMinify(buf, 3); + TEST_ASSERT_EQUAL_UINT32(3, count); + TEST_ASSERT_EQUAL_UINT8('a', (uint8_t)buf[0]); + TEST_ASSERT_EQUAL_UINT8('b', (uint8_t)buf[1]); + TEST_ASSERT_EQUAL_UINT8('c', (uint8_t)buf[2]); + TEST_ASSERT_EQUAL_UINT8('X', (uint8_t)buf[3]); +} + +static void testUtilsMinifyWriteTerminatorWhenSpaceRemain(void) +{ + /* + * textLen=4 时,压缩后 "abc" 长度为 3: + * - 返回值应为 3 + * - 因为 3 < 4,函数应在 buf[3] 写入 '\0' + * - buf[4] 是哨兵,必须保持不变 + */ + char buf[5]; + buf[0] = 'a'; + buf[1] = ' '; + buf[2] = 'b'; + buf[3] = 'c'; + buf[4] = '#'; + + uint32_t count = RyanJsonMinify(buf, 4); + TEST_ASSERT_EQUAL_UINT32(3, count); + TEST_ASSERT_EQUAL_UINT8('a', (uint8_t)buf[0]); + TEST_ASSERT_EQUAL_UINT8('b', (uint8_t)buf[1]); + TEST_ASSERT_EQUAL_UINT8('c', (uint8_t)buf[2]); + TEST_ASSERT_EQUAL_UINT8('\0', (uint8_t)buf[3]); + TEST_ASSERT_EQUAL_UINT8('#', (uint8_t)buf[4]); +} + +static void testUtilsVarargsPathHelpers(void) +{ + RyanJson_t root = RyanJsonCreateObject(); + + RyanJson_t objA = RyanJsonCreateObject(); + RyanJson_t objB = RyanJsonCreateObject(); + RyanJsonAddIntToObject(objB, "c", 3); + RyanJsonAddItemToObject(objA, "b", objB); + RyanJsonAddItemToObject(root, "a", objA); + + RyanJson_t c = RyanJsonGetObjectToKey(root, "a", "b", "c"); + TEST_ASSERT_NOT_NULL_MESSAGE(c, "GetObjectToKey 多级路径失败"); + TEST_ASSERT_EQUAL_INT_MESSAGE(3, RyanJsonGetIntValue(c), "多级路径取值错误"); + + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToKey(root, "a", "missing"), "不存在的路径应返回 NULL"); + + RyanJson_t arr = RyanJsonCreateArray(); + RyanJson_t sub = RyanJsonCreateArray(); + RyanJsonAddIntToArray(sub, 7); + RyanJsonAddItemToArray(arr, sub); + RyanJsonAddItemToObject(root, "arr", arr); + + RyanJson_t arrNode = RyanJsonGetObjectToKey(root, "arr"); + RyanJson_t v = RyanJsonGetObjectToIndex(arrNode, 0, 0); + TEST_ASSERT_NOT_NULL_MESSAGE(v, "GetObjectToIndex 多级索引失败"); + TEST_ASSERT_EQUAL_INT_MESSAGE(7, RyanJsonGetIntValue(v), "多级索引取值错误"); + + TEST_ASSERT_NULL_MESSAGE(RyanJsonGetObjectToIndex(arrNode, 1), "越界索引应返回 NULL"); + + RyanJsonDelete(root); +} + +void testUtilsRunner(void) +{ + UnitySetTestFile(__FILE__); + RUN_TEST(testUtilsBasic); + RUN_TEST(testUtilsMinifyNoExtraSpace); + RUN_TEST(testUtilsMinifyWriteTerminatorWhenSpaceRemain); + RUN_TEST(testUtilsVarargsPathHelpers); +} diff --git a/test/unityTest/common/FreeRTOSConfig.h b/test/unityTest/common/FreeRTOSConfig.h new file mode 100644 index 0000000..f005ac1 --- /dev/null +++ b/test/unityTest/common/FreeRTOSConfig.h @@ -0,0 +1,133 @@ +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +#include + +/** + * @brief RyanJson 单元测试的 FreeRTOS 内核配置入口(固定 Linux POSIX 端口)。 + * @note 该文件不是业务代码接口,而是 FreeRTOS 内核编译期配置表。 + * @details xmake 会编译 FreeRTOS Kernel + POSIX port,并由这里的宏控制调度、 + * 内存、断言和 API 裁剪行为。 + */ + +/** + * @brief 统一断言出口。 + * @note 具体实现由 `test/unityTest/runner/main.c` 提供。 + * @details 内核触发 `configASSERT()` 时会回调 `vAssertCalled()`,便于在单元测试日志里 + * 精确定位断言文件和行号。 + */ +extern void vAssertCalled(const char *file, int32_t line); +#define configASSERT(x) \ + do \ + { \ + if ((x) == 0) { vAssertCalled(__FILE__, (int32_t)__LINE__); } \ + } while (0) + +/** + * @brief 调度与时基配置。 + * @note 这里采用抢占式调度和 1ms Tick,优先覆盖并发场景下的测试行为。 + * @details `configCHECK_HANDLER_INSTALLATION` 在支持的移植层上可增加中断处理器安装检查。 + */ +#define configUSE_PREEMPTION 1 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configUSE_TIME_SLICING 1 +#define configUSE_16_BIT_TICKS 0 +#define configUSE_TICK_HOOK 0 +#define configUSE_IDLE_HOOK 0 +#define configCHECK_HANDLER_INSTALLATION 1 +#define configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 1 + +#define configCPU_CLOCK_HZ 1000000UL +#define configTICK_RATE_HZ 1000UL +#define configMAX_PRIORITIES 16 +#define configMINIMAL_STACK_SIZE 256U +#define configMAX_TASK_NAME_LEN 32 +#define configIDLE_SHOULD_YIELD 1 +#define configRECORD_STACK_HIGH_ADDRESS 1 + +/** + * @brief 内核对象能力开关。 + * @note 测试模式下尽量打开常用同步原语,覆盖更多内核交互路径。 + */ +#define configUSE_TASK_NOTIFICATIONS 1 +#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configUSE_QUEUE_SETS 1 +#define configUSE_APPLICATION_TASK_TAG 1 +#define configQUEUE_REGISTRY_SIZE 16 +#define configENABLE_BACKWARD_COMPATIBILITY 0 +#define configUSE_MINI_LIST_ITEM 0 +#define configSTACK_DEPTH_TYPE size_t +#define configMESSAGE_BUFFER_LENGTH_TYPE size_t + +/** + * @brief 诊断与观测能力。 + * @note 栈溢出与分配失败 hook 已在 runner 中实现,会在失败时直接终止并打日志。 + */ +#define configCHECK_FOR_STACK_OVERFLOW 2 +#define configUSE_MALLOC_FAILED_HOOK 1 +#define configUSE_SB_COMPLETED_CALLBACK 1 + +#define configUSE_TRACE_FACILITY 1 +#define configUSE_STATS_FORMATTING_FUNCTIONS 1 +#define configGENERATE_RUN_TIME_STATS 0 +#define configSTATS_BUFFER_MAX_LENGTH 0xFFFFU + +/** + * @brief 内存分配模型配置(固定 `heap_4`)。 + * @details + * - 单测链路统一使用 FreeRTOS `heap_4`(可释放且支持空闲块合并)。 + * - `heap_4` 依赖 `configTOTAL_HEAP_SIZE` 定义的静态堆区。 + * - `configHEAP_CLEAR_MEMORY_ON_FREE` 与 `configENABLE_HEAP_PROTECTOR` + * 可增强 UAF/越界类问题暴露能力。 + */ +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configAPPLICATION_ALLOCATED_HEAP 0 +#define configTOTAL_HEAP_SIZE (64U * 1024U * 1024U) +#define configHEAP_CLEAR_MEMORY_ON_FREE 1 +#define configENABLE_HEAP_PROTECTOR 1 + +#define configUSE_CO_ROUTINES 0 + +/** + * @brief 软件定时器配置。 + * @note 定时器任务使用较高优先级,避免测试中的计时行为被长时间饿死。 + */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) +#define configTIMER_QUEUE_LENGTH 20 +#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2U) + +/** + * @brief POSIX 端辅助能力。 + * @note 开启 `configUSE_POSIX_ERRNO` 后,每个任务可维护自己的 errno 语义。 + */ +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 +#define configUSE_POSIX_ERRNO 1 + +/** + * @brief API 暴露开关。 + * @note 测试目标倾向开启更多 API,便于未来补充内核交互类单测。 + */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_xTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_xTaskGetIdleTaskHandle 1 +#define INCLUDE_eTaskGetState 1 +#define INCLUDE_xTaskGetHandle 1 +#define INCLUDE_xTaskAbortDelay 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_uxTaskGetStackHighWaterMark2 1 +#define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskResumeFromISR 1 +#define INCLUDE_xSemaphoreGetMutexHolder 1 + +#endif diff --git a/test/unityTest/common/testCommon.c b/test/unityTest/common/testCommon.c new file mode 100644 index 0000000..04bdc8f --- /dev/null +++ b/test/unityTest/common/testCommon.c @@ -0,0 +1,232 @@ +#include "FreeRTOS.h" +#include "RyanJsonInternal.h" +#include "testCommon.h" +#include "tlsf.h" + +#if defined(RyanJsonTestPlatformQemu) +#define unitTlsfPoolSize (600U * 1024U) +#define unitTlsfPoolMin (512U * 1024U) +#else +#define unitTlsfPoolSize (1024U * 1024U) +#endif + +typedef struct +{ + tlsf_t tlsfHandle; + void *poolBuffer; + size_t poolSize; +} unityTestTlsfCtx_t; + +static unityTestTlsfCtx_t gUnityTestTlsfCtx = {NULL, NULL, 0U}; +#if defined(RyanJsonTestPlatformQemu) +static uint8_t gQemuTlsfPoolLogged = 0U; +#endif +static uint8_t gAllocSimConfigLogged = 0U; +static uint8_t gAllocSimEnabled = 0U; + +uint64_t platformUptimeMs(void) +{ + return testPlatformGetUptimeMs(); +} + +static size_t unityTestCalcSimulatedAllocSize(size_t requestSize) +{ + const size_t headerSize = (size_t)RyanJsonTestAllocHeaderSize; + const size_t alignSize = (size_t)RyanJsonTestAllocAlignSize; + size_t allocSize = requestSize; + + if (0U == requestSize) { return 0U; } + if (allocSize > (SIZE_MAX - headerSize)) { return 0U; } + + allocSize += headerSize; + if (alignSize > 1U) { allocSize = RyanJsonAlign(allocSize, alignSize); } + return allocSize; +} + +void unityTestSetAllocSimulation(uint8_t isEnable) +{ + if (0U != isEnable) { gAllocSimEnabled = 1U; } + else + { + gAllocSimEnabled = 0U; + } +} + +uint8_t unityTestGetAllocSimulation(void) +{ + return gAllocSimEnabled; +} + +static bool unityTestInitTlsf(void) +{ + if (NULL == gUnityTestTlsfCtx.poolBuffer) + { + size_t trySize = unitTlsfPoolSize; +#if defined(RyanJsonTestPlatformQemu) + while (trySize >= unitTlsfPoolMin) + { + gUnityTestTlsfCtx.poolBuffer = v_malloc(trySize); + if (NULL != gUnityTestTlsfCtx.poolBuffer) + { + gUnityTestTlsfCtx.poolSize = trySize; + break; + } + trySize /= 2U; + } +#else + gUnityTestTlsfCtx.poolBuffer = v_malloc(trySize); + if (NULL != gUnityTestTlsfCtx.poolBuffer) { gUnityTestTlsfCtx.poolSize = trySize; } +#endif + if ((NULL == gUnityTestTlsfCtx.poolBuffer) || (0U == gUnityTestTlsfCtx.poolSize)) { return false; } + } + + gUnityTestTlsfCtx.tlsfHandle = + tlsf_create_with_pool(gUnityTestTlsfCtx.poolBuffer, gUnityTestTlsfCtx.poolSize, gUnityTestTlsfCtx.poolSize); + return (NULL != gUnityTestTlsfCtx.tlsfHandle); +} + +void showMemoryInfo(void) +{ + size_t total = 0U; + size_t used = 0U; + size_t maxUsed = 0U; + + if (NULL == gUnityTestTlsfCtx.tlsfHandle) + { + testLog("%s:%d tlsf 未初始化\r\n", __FILE__, __LINE__); + return; + } + + tlsf_memory_info(gUnityTestTlsfCtx.tlsfHandle, &total, &used, &maxUsed); + testLog("%s:%d tlsf used: %lu, maxUsed: %lu, total: %lu\r\n", __FILE__, __LINE__, (unsigned long)used, (unsigned long)maxUsed, + (unsigned long)total); +} + +void logTaskStackRuntimeInfoByHandle(const char *tag, const char *taskName, TaskHandle_t taskHandle) +{ + const char *safeTag = ""; + const char *safeTaskName = ""; + TaskStatus_t taskStatus = {0}; + UBaseType_t taskPriority = 0U; + configSTACK_DEPTH_TYPE stackTotalWords = 0U; + configSTACK_DEPTH_TYPE stackFreeMinWords = 0U; + size_t stackUsedPeakWords = 0U; + size_t stackTotalBytes = 0U; + size_t stackFreeMinBytes = 0U; + size_t stackUsedPeakBytes = 0U; + + if (NULL != tag) { safeTag = tag; } + + if (NULL == taskHandle) + { + testLog("\n[%s] 任务句柄为空,无法获取任务信息\n", safeTag); + return; + } + + vTaskGetInfo(taskHandle, &taskStatus, pdTRUE, eInvalid); + if (NULL != taskName) { safeTaskName = taskName; } + else if (NULL != taskStatus.pcTaskName) { safeTaskName = taskStatus.pcTaskName; } + taskPriority = taskStatus.uxCurrentPriority; + stackFreeMinWords = taskStatus.usStackHighWaterMark; + +#if (portSTACK_GROWTH < 0) + if ((NULL != taskStatus.pxStackBase) && (NULL != taskStatus.pxEndOfStack) && (taskStatus.pxEndOfStack >= taskStatus.pxStackBase)) + { + stackTotalWords = (configSTACK_DEPTH_TYPE)(taskStatus.pxEndOfStack - taskStatus.pxStackBase + 1U); + } +#elif (portSTACK_GROWTH > 0) + if ((NULL != taskStatus.pxStackBase) && (NULL != taskStatus.pxEndOfStack) && (taskStatus.pxStackBase >= taskStatus.pxEndOfStack)) + { + stackTotalWords = (configSTACK_DEPTH_TYPE)(taskStatus.pxStackBase - taskStatus.pxEndOfStack + 1U); + } +#endif + + if (stackTotalWords >= stackFreeMinWords) { stackUsedPeakWords = stackTotalWords - stackFreeMinWords; } + + stackTotalBytes = stackTotalWords * sizeof(StackType_t); + stackFreeMinBytes = stackFreeMinWords * sizeof(StackType_t); + stackUsedPeakBytes = stackUsedPeakWords * sizeof(StackType_t); + + testLog("\n[%s] 任务=%s, Tick=%lu, 优先级=%lu, 栈总量=%lu(字)/%lu字节, 已用峰值=%lu(字)/%lu字节, 栈最小剩余=%lu(字)/%lu字节\n", + safeTag, safeTaskName, (unsigned long)xTaskGetTickCount(), (unsigned long)taskPriority, (unsigned long)stackTotalWords, + (unsigned long)stackTotalBytes, (unsigned long)stackUsedPeakWords, (unsigned long)stackUsedPeakBytes, + (unsigned long)stackFreeMinWords, (unsigned long)stackFreeMinBytes); +} + +int32_t unityTestGetUse(void) +{ + size_t total = 0U; + size_t used = 0U; + size_t maxUsed = 0U; + + if (NULL == gUnityTestTlsfCtx.tlsfHandle) { return 0; } + tlsf_memory_info(gUnityTestTlsfCtx.tlsfHandle, &total, &used, &maxUsed); + return (int32_t)used; +} + +void *unityTestMalloc(size_t size) +{ + size_t allocSize = 0U; + + if (NULL == gUnityTestTlsfCtx.tlsfHandle || 0U == size) { return NULL; } + + if (0U != gAllocSimEnabled) { allocSize = unityTestCalcSimulatedAllocSize(size); } + else + { + allocSize = size; + } + + return tlsf_malloc(gUnityTestTlsfCtx.tlsfHandle, allocSize); +} + +void unityTestFree(void *block) +{ + if (NULL == gUnityTestTlsfCtx.tlsfHandle || NULL == block) { return; } + tlsf_free(gUnityTestTlsfCtx.tlsfHandle, block); +} + +void *unityTestRealloc(void *block, size_t size) +{ + size_t allocSize = 0U; + + if (NULL == gUnityTestTlsfCtx.tlsfHandle) { return NULL; } + if (0U == size) { return tlsf_realloc(gUnityTestTlsfCtx.tlsfHandle, block, 0U); } + + if (0U != gAllocSimEnabled) { allocSize = unityTestCalcSimulatedAllocSize(size); } + else + { + allocSize = size; + } + + return tlsf_realloc(gUnityTestTlsfCtx.tlsfHandle, block, allocSize); +} + +void ryanJsonTestSetup(void) +{ + if (!unityTestInitTlsf()) + { + testLog("%s:%d tlsf 初始化失败\r\n", __FILE__, __LINE__); + return; + } +#if defined(RyanJsonTestPlatformQemu) + if (0U == gQemuTlsfPoolLogged) + { + testLog("[QEMU][MEM] tlsfPoolSize=%lu\r\n", (unsigned long)gUnityTestTlsfCtx.poolSize); + gQemuTlsfPoolLogged = 1U; + } +#endif + if (0U == gAllocSimConfigLogged) + { + testLog("[MEM][SIM] header=%lu align=%lu\r\n", (unsigned long)RyanJsonTestAllocHeaderSize, + (unsigned long)RyanJsonTestAllocAlignSize); + gAllocSimConfigLogged = 1U; + } + + xPortResetHeapMinimumEverFreeHeapSize(); + RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc); +} + +void ryanJsonTestTeardown(void) +{ + gUnityTestTlsfCtx.tlsfHandle = NULL; +} diff --git a/test/RyanJsonTest.h b/test/unityTest/common/testCommon.h similarity index 58% rename from test/RyanJsonTest.h rename to test/unityTest/common/testCommon.h index fbf3d8d..1e070dd 100644 --- a/test/RyanJsonTest.h +++ b/test/unityTest/common/testCommon.h @@ -1,5 +1,5 @@ -#ifndef __RyanJsonTest__ -#define __RyanJsonTest__ +#ifndef RYANJSON_TEST_COMMON_H +#define RYANJSON_TEST_COMMON_H #ifdef __cplusplus extern "C" { @@ -10,42 +10,63 @@ extern "C" { #include #include #include -#include -#include #include +#include +#include "FreeRTOS.h" +#include "task.h" +#include "unity.h" #include "valloc.h" #include "RyanJson.h" -#include "RyanJsonUtils.h" #include "cJSON.h" #include "yyjson.h" +#include "testPlatform.h" + +#ifndef RyanJsonTestAllocHeaderSize +#define RyanJsonTestAllocHeaderSize RyanJsonMallocHeaderSize +#endif + +#ifndef RyanJsonTestAllocAlignSize +#define RyanJsonTestAllocAlignSize RyanJsonMallocAlign +#endif + +static inline void delay(uint32_t ms) +{ + testPlatformSleepMs(ms); +} #define getArraySize(arr) ((int32_t)(sizeof(arr) / sizeof((arr)[0]))) #define checkMemory \ do \ { \ - int area = 0, use = 0; \ - v_mcheck(&area, &use); \ - if (area != 0 || use != 0) \ + if (0 != unityTestGetUse()) \ { \ - RyanMqttLog_e("内存泄漏"); \ + testLog("内存泄漏\r\n"); \ while (1) \ { \ - v_mcheck(&area, &use); \ - RyanMqttLog_e("|||----------->>> area = %d, size = %d", area, use); \ + showMemoryInfo(); \ delay(3000); \ } \ } \ } while (0) // 定义枚举类型 - +extern void *unityTestMalloc(size_t size); +extern void unityTestFree(void *block); +extern void *unityTestRealloc(void *block, size_t size); +extern void unityTestSetAllocSimulation(uint8_t isEnable); +extern uint8_t unityTestGetAllocSimulation(void); +extern int32_t unityTestGetUse(void); +extern void showMemoryInfo(void); +extern void logTaskStackRuntimeInfoByHandle(const char *tag, const char *taskName, TaskHandle_t taskHandle); // 定义结构体类型 +extern uint64_t platformUptimeMs(void); -/* extern variables-----------------------------------------------------------*/ extern RyanJsonBool_e RyanJsonExample(void); -extern RyanJsonBool_e RyanJsonBaseTest(void); -extern RyanJsonBool_e RFC8259JsonTest(void); -extern RyanJsonBool_e RyanJsonMemoryFootprintTest(void); +extern RyanJsonBool_e RyanJsonTestFun(void); + +extern void ryanJsonTestSetup(void); +extern void ryanJsonTestTeardown(void); + #ifdef __cplusplus } #endif diff --git a/test/unityTest/common/testPlatform.h b/test/unityTest/common/testPlatform.h new file mode 100644 index 0000000..d360c21 --- /dev/null +++ b/test/unityTest/common/testPlatform.h @@ -0,0 +1,78 @@ +#ifndef RYANJSON_TEST_PLATFORM_H +#define RYANJSON_TEST_PLATFORM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +typedef void (*testPlatformThreadEntry_t)(void *arg); +typedef int32_t (*testPlatformLogV_t)(const char *fmt, va_list args); +typedef uint64_t (*testPlatformGetUptimeMs_t)(void); +typedef void (*testPlatformSleepMs_t)(uint32_t ms); +/* stackDepthWords 使用 FreeRTOS 栈单位(StackType_t 个数),不是字节。 */ +typedef int32_t (*testPlatformRunThreadWithStackSize_t)(testPlatformThreadEntry_t entry, void *arg, size_t stackDepthWords); + +typedef struct +{ + testPlatformLogV_t logV; + testPlatformGetUptimeMs_t getUptimeMs; + testPlatformSleepMs_t sleepMs; + testPlatformRunThreadWithStackSize_t runThreadWithStackSize; +} testPlatformOps_t; + +// 平台函数表由 runner/main.c 提供默认实现;RTOS 可在启动时覆盖。 +extern testPlatformOps_t gTestPlatformOps; + +static inline void setTestPlatformOps(const testPlatformOps_t *ops) +{ + if (NULL != ops) { gTestPlatformOps = *ops; } +} + +static inline testPlatformOps_t *getTestPlatformOps(void) +{ + return &gTestPlatformOps; +} + +static inline int32_t testLog(const char *fmt, ...) +{ + int32_t ret = 0; + va_list args; + testPlatformLogV_t logFunc = gTestPlatformOps.logV; + if (NULL == logFunc || NULL == fmt) { return -1; } + + va_start(args, fmt); + ret = logFunc(fmt, args); + va_end(args); + return ret; +} + +static inline uint64_t testPlatformGetUptimeMs(void) +{ + testPlatformGetUptimeMs_t getUptimeMsFunc = gTestPlatformOps.getUptimeMs; + if (NULL == getUptimeMsFunc) { return 0U; } + return getUptimeMsFunc(); +} + +static inline void testPlatformSleepMs(uint32_t ms) +{ + testPlatformSleepMs_t sleepMsFunc = gTestPlatformOps.sleepMs; + if (NULL == sleepMsFunc) { return; } + sleepMsFunc(ms); +} + +static inline int32_t testPlatformRunThreadWithStackSize(testPlatformThreadEntry_t entry, void *arg, size_t stackDepthWords) +{ + testPlatformRunThreadWithStackSize_t runThreadFunc = gTestPlatformOps.runThreadWithStackSize; + if (NULL == runThreadFunc || NULL == entry) { return -1; } + return runThreadFunc(entry, arg, stackDepthWords); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/test/unityTest/common/unity_config.h b/test/unityTest/common/unity_config.h new file mode 100644 index 0000000..3859ede --- /dev/null +++ b/test/unityTest/common/unity_config.h @@ -0,0 +1,97 @@ +#ifndef UNITY_CONFIG_H +#define UNITY_CONFIG_H + +#include + +/* 输出与格式 */ +#define UNITY_OUTPUT_COLOR // 开启彩色打印 +#define UNITY_INCLUDE_PRINT_FORMATTED // 开启 UnityPrintF 支持 +#define UNITY_INCLUDE_EXEC_TIME // 开启测试执行时间统计 +#define UNITY_DIFFERENTIATE_FINAL_FAIL // 最终汇总显示 FAILED + +// #define UNITY_OUTPUT_FOR_QT_CREATOR // 输出 file://path:line,方便点击跳转 +// #define UNITY_OUTPUT_FOR_ECLIPSE // 输出 Eclipse 友好的格式 +// #define UNITY_OUTPUT_FOR_IAR_WORKBENCH // 输出 IAR Workbench 友好的格式 + +// #define UNITY_OUTPUT_CHAR(a) my_putchar(a) // 替换输出字符函数 +// #define UNITY_OUTPUT_CHAR_HEADER_DECLARATION my_putchar // 声明输出函数(仅声明,不定义) +// #define UNITY_OMIT_OUTPUT_CHAR_HEADER_DECLARATION // 不在头文件中声明输出函数 + +// #define UNITY_OUTPUT_FLUSH() my_flush() // 替换 flush +// #define UNITY_OUTPUT_FLUSH_HEADER_DECLARATION my_flush // 声明 flush 函数 +// #define UNITY_USE_FLUSH_STDOUT // flush 使用 fflush(stdout) + +// #define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n') // 自定义换行输出 +// #define UNITY_OUTPUT_START() // 测试开始前回调 +// #define UNITY_OUTPUT_COMPLETE() // 测试结束后回调 +// #define UNITY_PRINT_TEST_CONTEXT() // 失败时输出上下文 + +/* 整数/指针宽度与头文件 */ +#define UNITY_SUPPORT_64 // 开启 64 位整数支持 +#define UNITY_INT_WIDTH 32 // int 位宽(手动指定时使用) +#define UNITY_UINT_WIDTH 32 // uint 位宽(Unity 默认未使用,可保留) +// #define UNITY_LONG_WIDTH 32 // long 位宽 +// #define UNITY_POINTER_WIDTH 32 // 指针位宽 + +// #define UNITY_COUNTER_TYPE uint16_t // 计数器类型(测试数量较大时可调大) +// #define UNITY_LINE_TYPE uint16_t // 行号类型(大文件可调大) + +// #define UNITY_EXCLUDE_STDINT_H // 不包含 +// #define UNITY_EXCLUDE_LIMITS_H // 不包含 +// #define UNITY_EXCLUDE_STDDEF_H // 不包含 +// #define UNITY_EXCLUDE_MATH_H // 不包含 +// #define UNITY_EXCLUDE_SETJMP_H // 不包含 + +/* 浮点配置 */ +#define UNITY_INCLUDE_DOUBLE // 开启 double 支持 +// #define UNITY_EXCLUDE_DOUBLE // 禁用 double(默认) +// #define UNITY_DOUBLE_TYPE double // 自定义 double 类型 +// #define UNITY_DOUBLE_PRECISION 1e-12 // double 误差 + +// #define UNITY_EXCLUDE_FLOAT // 禁用 float +// #define UNITY_FLOAT_TYPE float // 自定义 float 类型 +// #define UNITY_FLOAT_PRECISION 1e-6f // float 误差 +// #define UNITY_EXCLUDE_FLOAT_PRINT // 禁止浮点打印(减小体积) + +// #define UNITY_NAN_NOT_EQUAL_NAN // NaN 与 NaN 比较为不相等 +// #define UNITY_ROUND_TIES_AWAY_FROM_ZERO // 浮点四舍五入策略 +// #define UNITY_IS_NAN(x) my_isnan(x) // 自定义 NaN 判断 +// #define UNITY_IS_INF(x) my_isinf(x) // 自定义 Inf 判断 + +/* 细节/诊断信息 */ +// #define UNITY_EXCLUDE_DETAILS // 关闭细节栈 +// #define UNITY_DETAIL1_NAME "Function" // 细节1名字 +// #define UNITY_DETAIL2_NAME "Argument" // 细节2名字 +// #define UNITY_DETAIL_STACK_SIZE 8 // 细节栈大小(启用后用栈保存) +// #define UNITY_DETAIL_LABEL_TYPE uint8_t +// #define UNITY_DETAIL_VALUE_TYPE uint32_t +// #define UNITY_DETAIL_LABEL_NAMES {0, "Func", "Arg"} // 自定义标签名 + +/* 执行时间统计 */ +// #define UNITY_TIME_TYPE uint32_t // 时间类型 +// #define UNITY_CLOCK_MS() my_clock_ms() // 返回毫秒 +// #define UNITY_EXEC_TIME_START() do { } while (0) // 自定义开始计时 +// #define UNITY_EXEC_TIME_STOP() do { } while (0) // 自定义停止计时 +// #define UNITY_PRINT_EXEC_TIME() do { } while (0) // 自定义打印耗时 + +/* 测试用例/Runner */ +// #define UNITY_USE_COMMAND_LINE_ARGS // 支持命令行参数 +// #define UNITY_SKIP_DEFAULT_RUNNER // 禁用默认 Runner +// #define UNITY_SUPPORT_TEST_CASES // 启用 TEST_CASE 宏 + +/* Shorthand 行为 */ +// #define UNITY_SHORTHAND_AS_OLD // 旧版 shorthand +// #define UNITY_SHORTHAND_AS_INT // 强制按 int 解释 +// #define UNITY_SHORTHAND_AS_MEM // 强制按 memory 解释 +// #define UNITY_SHORTHAND_AS_RAW // 原始不转换 +// #define UNITY_SHORTHAND_AS_NONE // 禁用 shorthand + +/* 平台/内存 */ +// #define UNITY_PROGMEM // PROGMEM 支持(如 AVR) +// #define UNITY_PTR_ATTRIBUTE // 指针属性修饰 +// #define UNITY_INTERNAL_PTR // 内部指针类型 + +/* 特殊行为 */ +// #define UNITY_COMPARE_PTRS_ON_ZERO_ARRAY // 允许对长度为 0 的数组做指针比较 + +#endif /* UNITY_CONFIG_H */ diff --git a/test/unityTest/include/testBase.h b/test/unityTest/include/testBase.h new file mode 100644 index 0000000..64e8a04 --- /dev/null +++ b/test/unityTest/include/testBase.h @@ -0,0 +1,65 @@ +#ifndef RYANJSON_TEST_BASE_H +#define RYANJSON_TEST_BASE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include + +#include "RyanJson.h" +#include "RyanJsonInternal.h" +#include "cJSON.h" +#include "valloc.h" +#include "testCommon.h" + +#undef jsonLog +#define jsonLog(fmt, ...) testLog("%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__) + +// 定义枚举类型 + +// 定义结构体类型 + +/* extern variables-----------------------------------------------------------*/ + +extern void printJsonDebug(RyanJson_t json); +extern void rootNodeCheckTest(RyanJson_t json); +extern void itemNodeCheckTest(RyanJson_t json); +extern void arrayNodeCheckTest(RyanJson_t json, RyanJsonBool_e isReversed); +extern void arrayItemNodeCheckTest(RyanJson_t json); +extern void testCheckRoot(RyanJson_t pJson); +extern void testCheckRootEx(RyanJson_t pJson, RyanJsonBool_e isReversed); + +extern void testChangeRunner(void); +extern void testCompareRunner(void); +extern void testCreateRunner(void); +extern void testDeleteRunner(void); +extern void testDetachRunner(void); +extern void testDuplicateRunner(void); +extern void testForEachRunner(void); +extern void testLoadSuccessRunner(void); +extern void testLoadFailureRunner(void); +extern void testReplaceRunner(void); +extern void testPrintRunner(void); +extern void testMemoryRunner(void); +extern void testDeepRecursionRunner(void); + +extern void testEqualityBoolRunner(void); +extern void testEqualityDoubleRunner(void); +extern void testEqualityIntRunner(void); +extern void testEqualityStringRunner(void); + +extern void testUtilsRunner(void); +extern void testRobustRunner(void); +extern void testStressRunner(void); +extern void testRfc8259Runner(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/test/unityTest/runner/main.c b/test/unityTest/runner/main.c new file mode 100644 index 0000000..4c28bca --- /dev/null +++ b/test/unityTest/runner/main.c @@ -0,0 +1,293 @@ +#include "FreeRTOS.h" +#include "RyanJson.h" +#include "task.h" +#include "../../common/testCommon.h" +#include "testBase.h" + +#include +#include +#include + +#if defined(RyanJsonTestPlatformQemu) +#include "qemuPlatform.h" +#endif + +#if defined(RyanJsonFreeRtosHeap4) +void vApplicationGetRandomHeapCanary(portPOINTER_SIZE_TYPE *heapCanary) +{ + portPOINTER_SIZE_TYPE seed = (portPOINTER_SIZE_TYPE)(uintptr_t)&vApplicationGetRandomHeapCanary; + + if (NULL == heapCanary) { return; } +#if defined(RyanJsonTestPlatformQemu) + seed ^= (portPOINTER_SIZE_TYPE)xTaskGetTickCount(); +#else + struct timespec ts = {0}; + if (0 == clock_gettime(CLOCK_MONOTONIC, &ts)) + { + seed ^= (portPOINTER_SIZE_TYPE)(uintptr_t)ts.tv_sec; + seed ^= (portPOINTER_SIZE_TYPE)(uintptr_t)ts.tv_nsec; + } +#endif + seed ^= (portPOINTER_SIZE_TYPE)(uintptr_t)heapCanary; + if (0U == seed) { seed = (portPOINTER_SIZE_TYPE)0xA5A5A5A5U; } + *heapCanary = seed; +} +#endif + +static int32_t defaultTestPlatformLogV(const char *fmt, va_list args) +{ + if (NULL == fmt) { return -1; } +#if defined(RyanJsonTestPlatformQemu) + char logBuffer[1024]; + int32_t logLen = (int32_t)vsnprintf(logBuffer, sizeof(logBuffer), fmt, args); + if (logLen > 0) { qemuUartWrite(logBuffer); } + return logLen; +#else + return vprintf(fmt, args); +#endif +} + +static uint64_t defaultTestPlatformGetUptimeMs(void) +{ + return (uint64_t)xTaskGetTickCount() * (uint64_t)portTICK_PERIOD_MS; +} + +static void defaultTestPlatformSleepMs(uint32_t ms) +{ + if (0U == ms) { return; } + vTaskDelay(pdMS_TO_TICKS(ms)); +} + +typedef struct +{ + testPlatformThreadEntry_t threadEntry; + void *threadArg; + TaskHandle_t waitTask; +} testPlatformThreadCtx_t; + +static void defaultTestPlatformThreadTask(void *arg) +{ + testPlatformThreadCtx_t *threadCtx = (testPlatformThreadCtx_t *)arg; + + if (NULL != threadCtx && NULL != threadCtx->threadEntry) { threadCtx->threadEntry(threadCtx->threadArg); } + if (NULL != threadCtx && NULL != threadCtx->waitTask) { xTaskNotifyGive(threadCtx->waitTask); } + vTaskDelete(NULL); +} + +static int32_t defaultTestPlatformRunThreadWithStackSize(testPlatformThreadEntry_t entry, void *arg, size_t stackDepthWords) +{ + BaseType_t createRet = pdFAIL; + TaskHandle_t waitTask = xTaskGetCurrentTaskHandle(); + configSTACK_DEPTH_TYPE taskStackDepth = 0U; + testPlatformThreadCtx_t threadCtx = {0}; + + if (NULL == entry || NULL == waitTask) { return EINVAL; } + + if (0U == stackDepthWords) { taskStackDepth = (configSTACK_DEPTH_TYPE)configMINIMAL_STACK_SIZE; } + else + { + if (stackDepthWords < (size_t)configMINIMAL_STACK_SIZE) { stackDepthWords = (size_t)configMINIMAL_STACK_SIZE; } + if (stackDepthWords > (size_t)(~(configSTACK_DEPTH_TYPE)0U)) { stackDepthWords = (size_t)(~(configSTACK_DEPTH_TYPE)0U); } + taskStackDepth = (configSTACK_DEPTH_TYPE)stackDepthWords; + } + + threadCtx.threadEntry = entry; + threadCtx.threadArg = arg; + threadCtx.waitTask = waitTask; + + createRet = xTaskCreate(defaultTestPlatformThreadTask, "unitCase", taskStackDepth, &threadCtx, + (UBaseType_t)(configMAX_PRIORITIES - 3U), NULL); + if (pdPASS != createRet) { return ENOMEM; } + + (void)ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + return 0; +} + +static int32_t gUnitTestResult = 1; + +#if defined(RyanJsonTestPlatformQemu) +static uint32_t qemuLoadWordViaAsm(const void *address) +{ + uint32_t value = 0U; + __asm volatile("ldr %0, [%1]" : "=r"(value) : "r"(address)); + return value; +} + +static void qemuForceUnalignedFaultViaAsm(const void *address) +{ + /* LDRD on unaligned address should trap on Cortex-M models that implement it. */ + __asm volatile("ldrd r2, r3, [%0]" : : "r"(address) : "r2", "r3", "memory"); +} + +static void qemuRunPostUnitAlignmentCheck(void) __attribute__((noreturn)); +static void qemuRunPostUnitAlignmentCheck(void) +{ + uint32_t alignedWords[2] = {0x12345678UL, 0xAABBCCDDUL}; + uint32_t readBack = qemuLoadWordViaAsm((const void *)&alignedWords[1]); + uint8_t raw[8] __attribute__((aligned(4))) = {0x11U, 0x22U, 0x33U, 0x44U, 0x55U, 0x66U, 0x77U, 0x88U}; + void const *unalignedAddr = (void const *)(raw + 1U); + + if (readBack != alignedWords[1]) + { + testLog("[QEMU][ALIGN] aligned_access FAIL read=0x%08lx expected=0x%08lx\n", (unsigned long)readBack, + (unsigned long)alignedWords[1]); + qemuRequestExit(1); + } + + testLog("[QEMU][ALIGN] aligned_access PASS read=0x%08lx\n", (unsigned long)readBack); + qemuSetExpectUnalignedFault(true); + testLog("[QEMU][ALIGN] unaligned_access TRIGGER addr=0x%08lx\n", (unsigned long)(uintptr_t)unalignedAddr); + qemuForceUnalignedFaultViaAsm(unalignedAddr); + +#if defined(RyanJsonQemuSoftUnalignedTrap) + if ((((uintptr_t)unalignedAddr) & 0x3U) != 0U) + { + testLog("[QEMU][ALIGN] fallback_soft_trap\n"); + testLog("[QEMU][HARDFAULT] fallback_soft_trap_no_hw_fault addr=0x%08lx\n", (unsigned long)(uintptr_t)unalignedAddr); + testLog("[QEMU][RESULT] EXPECTED_UNALIGNED_FAULT fallbackAddr=0x%08lx\n", (unsigned long)(uintptr_t)unalignedAddr); + qemuRequestExit(0); + } +#endif + + qemuSetExpectUnalignedFault(false); + testLog("[QEMU][ALIGN] unaligned_access did not fault\n"); + testLog("[QEMU][RESULT] UNIT_FAIL code=%ld\n", (long)1L); + qemuRequestExit(1); +} +#endif + +void vAssertCalled(const char *file, int32_t line) +{ + testLog("\n[FreeRTOS ASSERT] %s:%ld\n", (NULL != file) ? file : "", (long)line); + abort(); +} + +void vApplicationMallocFailedHook(void) +{ + testLog("\n[FreeRTOS] Malloc Failed Hook\n"); + vAssertCalled(__FILE__, (int32_t)__LINE__); +} + +void vApplicationStackOverflowHook(TaskHandle_t task, char *taskName) +{ + (void)task; + testLog("\n[FreeRTOS] Stack Overflow Hook: %s\n", (NULL != taskName) ? taskName : ""); + vAssertCalled(__FILE__, (int32_t)__LINE__); +} + +testPlatformOps_t gTestPlatformOps = { + .logV = defaultTestPlatformLogV, + .getUptimeMs = defaultTestPlatformGetUptimeMs, + .sleepMs = defaultTestPlatformSleepMs, + .runThreadWithStackSize = defaultTestPlatformRunThreadWithStackSize, +}; + +static int32_t baselineUsed = 0; + +void setUp(void) +{ + ryanJsonTestSetup(); + baselineUsed = unityTestGetUse(); +} + +void tearDown(void) +{ + int32_t used = unityTestGetUse(); + if (used != baselineUsed) + { + testLog("\n\033[1;31m[MEMORY LEAK] Test '%s' leaked %d bytes!\033[0m\n", Unity.CurrentTestName, used - baselineUsed); + showMemoryInfo(); + } + TEST_ASSERT_EQUAL_INT_MESSAGE(baselineUsed, used, "Memory Leak Detected"); + + ryanJsonTestTeardown(); +} + +void testRyanJsonExample(void) +{ + TEST_ASSERT_EQUAL(RyanJsonTrue, RyanJsonExample()); + RyanJsonInitHooks(unityTestMalloc, unityTestFree, unityTestRealloc); +} + +static int32_t runAllUnitTests(void) +{ + UnityBegin(__FILE__); + + RUN_TEST(testRyanJsonExample); + + testChangeRunner(); + testCompareRunner(); + testCreateRunner(); + testDeleteRunner(); + testDetachRunner(); + testDuplicateRunner(); + testForEachRunner(); + testLoadSuccessRunner(); + testLoadFailureRunner(); + testReplaceRunner(); + + testEqualityBoolRunner(); + testEqualityDoubleRunner(); + testEqualityIntRunner(); + testEqualityStringRunner(); + + testUtilsRunner(); + testRobustRunner(); + testPrintRunner(); + testStressRunner(); +#if !defined(RyanJsonTestPlatformQemu) + testRfc8259Runner(); +#endif + testMemoryRunner(); + testDeepRecursionRunner(); + + return UnityEnd(); +} + +static void logUnitTaskRuntimeInfo(void) +{ + TaskHandle_t currentTask = xTaskGetCurrentTaskHandle(); + logTaskStackRuntimeInfoByHandle("unitMain", NULL, currentTask); +} + +static void unitTestTask(void *arg) +{ + (void)arg; + gUnitTestResult = runAllUnitTests(); + logUnitTaskRuntimeInfo(); + +#if defined(RyanJsonTestPlatformQemu) + if (0 == gUnitTestResult) + { + testLog("[QEMU][RESULT] UNIT_PASS code=%ld tick=%lu\n", (long)gUnitTestResult, (unsigned long)xTaskGetTickCount()); + qemuRunPostUnitAlignmentCheck(); + } + + testLog("[QEMU][RESULT] UNIT_FAIL code=%ld\n", (long)gUnitTestResult); + qemuRequestExit((0 == gUnitTestResult) ? 1 : gUnitTestResult); +#else + vTaskEndScheduler(); + vTaskDelete(NULL); +#endif +} + +#ifndef isEnableFuzzer +int32_t main(void) +{ + BaseType_t createRet = xTaskCreate(unitTestTask, // 任务入口函数 + "unitMain", // 任务名称 + 4096, // 任务栈大小 + NULL, // 任务参数 + (UBaseType_t)(configMAX_PRIORITIES - 2), // 任务优先级 + NULL // 不需要任务句柄 + ); + if (pdPASS != createRet) + { + testLog("[FreeRTOS] xTaskCreate(unitMain) failed\n"); + return 1; + } + + vTaskStartScheduler(); + return gUnitTestResult; +} +#endif diff --git a/xmake.lua b/xmake.lua index b1c270f..f9cb64b 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,134 +1,461 @@ -add_rules("plugin.compile_commands.autoupdate", {outputdir = ".vscode"}) -target("RyanJson",function() - set_kind("binary") - - -- set_toolchains("gcc") -- 确保使用 GCC - set_toolchains("clang") - set_plat("linux") - set_arch("x86") - set_languages("gnu99") -- 关键!启用 GNU 扩展 - add_defines("isEnableFuzzer") - add_cxflags("-fsanitize=fuzzer", "-fprofile-instr-generate", "-fcoverage-mapping", {force = true} ) - add_ldflags("-fsanitize=fuzzer", "-fprofile-instr-generate", "-fcoverage-mapping", {force = true} ) - - set_policy("build.ccache", false) - -- set_optimize("smallest") -- -Os - -- set_optimize("faster") -- -O2 - set_optimize("fastest") -- -O3 - -- set_optimize("aggressive") -- -Ofast - - -- 启用全部警告 - set_warnings("everything") -- -Wall -Wextra -Weffc++ / -Weverything - - -- 链接器选项:生成 map 文件 - -- add_ldflags("-Wl,-Map=$(buildir)/RyanJson.map") - - -- 开启库加固(需与 -O2 以上配合) - -- add_defines("_FORTIFY_SOURCE=2") -- glibc 格式/内存函数加固 - - -- 链接器安全硬化与优化 - add_ldflags( - "-flto", -- 链接时优化(启用 LTO,便于 CFI 等) - "-fPIE", -- 位置无关可执行 - "-pie", -- 与 -fPIE 搭配,启用 ASLR - "-fno-omit-frame-pointer", -- 保留帧指针,便于崩溃分析 - "-fstack-clash-protection", -- 栈碰撞保护(平台支持时有效) - "-Wl,-z,relro", -- 只读重定位(硬化) - "-Wl,-z,now", -- 立即绑定(与 relro 搭配) - "-Wl,-z,noexecstack", -- 栈不可执行 - "-Wl,-z,separate-code", -- 代码与数据段分离 - {force = true} - ) - - -- Sanitizer 检测项(运行时错误) - add_ldflags( - "-fsanitize=address", -- 内存越界、释放后使用 - "-fsanitize=leak", -- 内存泄漏 - "-fsanitize=undefined", -- 未定义行为(除零、溢出、无效移位等) - "-fsanitize=pointer-compare", -- 无效指针比较 - "-fsanitize=pointer-subtract", -- 无效指针相减 - "-fsanitize=bounds", -- 数组越界 - "-fsanitize=float-divide-by-zero", -- 浮点除零 - "-fsanitize=float-cast-overflow", -- 浮点转整数溢出 - -- "-fsanitize=thread", -- 多线程数据竞争 - -- "-fsanitize=memory", -- 未初始化内存使用 - -- "-fsanitize=safe-stack", -- 栈分离机制 - -- "-fsanitize=cfi", -- 控制流完整性(需 LTO 与 Clang) - -- "-fsanitize=alignment", -- 检测未对齐的内存访问 - -- "-fno-sanitize=alignment", -- 某些平台不兼容 - {force = true} - ) - - -- 编译器警告与静态分析(开发期错误检测,Clang 兼容) - add_cxflags( - "-g3", -- 生成调试信息" - "-pedantic", -- 强制遵循 ISO C 标准 - "-Wall", -- 常见警告 - "-Wextra", -- 额外警告 - "-Wconversion", -- 隐式类型转换风险 - "-Wsign-conversion", -- 有符号/无符号转换风险 - "-Wdouble-promotion", -- float 自动提升为 double - "-Wstrict-prototypes", -- 函数声明必须带参数类型 - "-Wold-style-definition", -- 检测旧式函数定义 - "-Wimplicit-fallthrough", -- switch/case 未显式 fallthrough - "-Wshadow", -- 局部变量遮蔽 - "-Wcast-align", -- 类型转换对齐问题 - "-Wpointer-arith", -- 指针运算风险 - "-Warray-bounds", -- 数组越界访问 - "-Wshift-overflow", -- 位移造成的溢出 - "-Wformat-truncation", -- 格式化字符串被截断风险(替代 stringop-truncation) - "-Walloc-size", -- 分配大小问题(替代 alloc-zero) - "-Wnull-dereference", -- 空指针解引用 - "-Wtautological-compare", -- 恒真/恒假的比较 - "-Wstrict-overflow", -- 有符号溢出优化假设 - "-Wmissing-prototypes", -- 全局函数未在头文件声明 - "-Wmissing-declarations", -- 全局变量/函数未声明 - "-Wredundant-decls", -- 重复声明 - "-Wunreachable-code", -- 不可达代码 - "-Wtype-limits", -- 比较恒真/恒假的表达式(如 unsigned < 0) - "-Wshift-negative-value", -- 对负数进行移位 - "-Wdiv-by-zero", -- 除以零(编译期可分析) - "-Wformat-security", -- 格式化字符串安全问题 - "-Wdisabled-optimization", -- 被禁用的优化 - "-Wreturn-local-addr", -- 返回局部变量地址 - "-Wdeprecated", -- 使用已弃用的特性 - -- "-Wunsafe-buffer-usage", -- 不安全的数组/指针用法(Clang 新增) - "-Wuninitialized", -- 使用未初始化变量 - "-fstack-protector-strong",-- 栈保护 - -- 进一步增强(可选,按需开启) - "-Wmissing-include-dirs", -- 头文件目录缺失 - "-Wcast-qual", -- 丢弃 const/volatile 限定符的转换 - "-Wconditional-uninitialized", -- 条件路径未初始化 - "-Wcovered-switch-default", -- default 覆盖所有枚举值 - "-Wformat-nonliteral", -- 非字面量格式串 - "-Wformat-signedness", -- 格式化与符号性不匹配 - "-Wvla", -- 可变长度数组(不安全/不建议使用) - "-fno-common", -- 禁止旧式多重定义(链接期更严格) - "-fno-strict-aliasing", -- 禁止严格别名优化,减少别名相关 UB 风险 - "-Wno-documentation", -- 临时变比 - "-Wno-parentheses-equality", - {force = true} - ) - - add_includedirs('./test/fuzzer', {public = true}) - add_files('./test/fuzzer/*.c', {public = true}) - - --加入代码和头文件 - add_includedirs('./RyanJson', {public = true}) - add_files('./RyanJson/*.c', {public = true}) - - add_includedirs('./example', {public = true}) - add_includedirs('./test', {public = true}) - add_includedirs('./test/valloc', {public = true}) - add_includedirs('./test/baseTest', {public = true}) - add_includedirs('./externalModule/cJSON', {public = true}) - add_includedirs('./externalModule/yyjson', {public = true}) - - add_files('./example/*.c', {public = true}) - add_files('./test/*.c', {public = true}, {cxflags = "-w"}) - add_files('./test/valloc/*.c', {public = true}, {cxflags = "-w"}) - add_files('./test/baseTest/*.c', {public = true}, {cxflags = "-w"}) - add_files('./externalModule/cJSON/*.c', {public = true}, {cxflags = "-w"}) - add_files('./externalModule/yyjson/*.c', {public = true}, {cxflags = "-w"}) - -end) \ No newline at end of file +-- 自动生成 compile_commands.json,方便 VSCode/Clangd 做代码补全与跳转 +add_rules("plugin.compile_commands.autoupdate", {outputdir = ".vscode"}) + +-- 从环境变量读取布尔宏值,支持: true/false, 1/0, on/off(大小写不敏感) +local function getBooleanEnvDefineValue(envKey, defaultValue) + local rawValue = os.getenv(envKey) + if nil == rawValue or "" == rawValue then + return defaultValue + end + + local lowerValue = rawValue:lower() + if "true" == lowerValue or "1" == lowerValue or "on" == lowerValue then + return "true" + end + if "false" == lowerValue or "0" == lowerValue or "off" == lowerValue then + return "false" + end + + print("warning: invalid " .. envKey .. "=" .. rawValue .. ", fallback to " .. defaultValue) + return defaultValue +end + +-- 从环境变量读取非负整数宏;不合法则打印 warning 并忽略。 +local function tryAddNumericEnvDefine(envKey, defineKey, minValue) + local rawValue = os.getenv(envKey) + if nil == rawValue or "" == rawValue then + return + end + + local numericValue = tonumber(rawValue) + if nil == numericValue then + print("warning: invalid " .. envKey .. "=" .. rawValue .. ", ignored") + return + end + + numericValue = math.floor(numericValue) + if numericValue < minValue then + print("warning: invalid " .. envKey .. "=" .. rawValue .. ", expected >= " .. tostring(minValue) .. ", ignored") + return + end + + add_defines(defineKey .. "=" .. tostring(numericValue)) +end + +-- 单测主线固定为 linux-freertos + heap_4 + +-- 把 yyjson 单独编译为静态库,避免每次业务宏变化都重编译其大体积源码 +-- 说明: +-- 1) yyjson 基本不依赖 RyanJson 的业务宏,拆分后可显著减少重复编译。 +-- 2) binary 目标通过 add_deps("yyjsonStatic") 链接该静态库。 +target("yyjsonStatic", function() + set_kind("static") + set_default(false) + + set_toolchains("clang") + set_plat("linux") + set_arch("x86") + set_languages("gnu99") + set_optimize("fastest") + + add_includedirs('./test/externalModule/yyjson', {public = true}) + add_files('./test/externalModule/yyjson/*.c', {public = true}, {cxflags = "-w"}) +end) + +-- 统一构建配置:两个 target 共享同一套流程,仅通过 isFuzz 切换差异 +local function setupRyanJsonTarget(isFuzz) + set_kind("binary") + + -- 编译工具链与平台配置 + set_toolchains("clang") + set_plat("linux") + set_arch("x86") + set_languages("gnu99") + + -- 编译优化策略 + set_policy("build.ccache", false) + set_optimize("fastest") + + -- 警告设置:启用所有警告(Clang 下相当于 -Weverything) + set_warnings("everything") + + -- 三个核心配置宏支持由环境变量覆盖: + -- RYANJSON_STRICT_OBJECT_KEY_CHECK + -- RYANJSON_DEFAULT_ADD_AT_HEAD + -- RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC + local strictObjectKeyCheck = getBooleanEnvDefineValue("RYANJSON_STRICT_OBJECT_KEY_CHECK", "false") + local defaultAddAtHead = getBooleanEnvDefineValue("RYANJSON_DEFAULT_ADD_AT_HEAD", "false") + local snprintfSupportScientific = getBooleanEnvDefineValue("RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC", "false") + + add_defines("RyanJsonStrictObjectKeyCheck=" .. strictObjectKeyCheck) + add_defines("RyanJsonDefaultAddAtHead=" .. defaultAddAtHead) + -- 声明 snprintf 支持科学计数法,影响 double 序列化策略 + add_defines("RyanJsonSnprintfSupportScientific=" .. snprintfSupportScientific) + -- 启用 Linux 测试环境分支(用于主机侧测试/兼容代码路径) + add_defines("RyanJsonLinuxTestEnv") + add_defines("RyanJsonTestPlatformLinuxFreeRtos") + add_defines("RyanJsonFreeRtosHeap4") + -- 向代码注入项目根目录,供测试与样例定位资源 + add_defines("RyanJsonProjectRootPath=\"$(projectdir)\"") + -- 让 Unity 使用项目自定义配置头 + add_defines("UNITY_INCLUDE_CONFIG_H") + -- 测试分配模拟参数(可选):用于评估不同 malloc 头部与对齐策略的影响。 + tryAddNumericEnvDefine("RYANJSON_TEST_ALLOC_HEADER_SIZE", "RyanJsonTestAllocHeaderSize", 0) + tryAddNumericEnvDefine("RYANJSON_TEST_ALLOC_ALIGN_SIZE", "RyanJsonTestAllocAlignSize", 1) + -- add_defines("RyanJsonEnableAssert") + + -- fuzz 专用差异:启用 libFuzzer 宏与链接参数(会注入 main) + if isFuzz then + add_defines("isEnableFuzzer") + -- 编译期启用 libFuzzer 插桩 + add_cxflags("-fsanitize=fuzzer", {force = true}) + -- 链接期注入 libFuzzer runtime(包含 fuzz main) + add_ldflags("-fsanitize=fuzzer", {force = true}) + end + + -- 覆盖率相关 + add_cxflags( + "-fprofile-instr-generate", -- 生成原始 profile 数据 + "-fcoverage-mapping", -- 生成源码到覆盖率映射 + {force = true} + ) + add_ldflags( + "-fprofile-instr-generate", -- 链接覆盖率 runtime + "-fcoverage-mapping", -- 链接覆盖率映射支持 + {force = true} + ) + + -- 链接器安全硬化与优化选项 + add_ldflags( + "-flto", -- 链接时优化(跨文件优化) + "-fPIE", -- 生成位置无关可执行代码 + "-pie", -- 启用 PIE 可执行文件 + "-fno-omit-frame-pointer", -- 保留栈帧,便于回溯定位 + "-fstack-clash-protection",-- 栈碰撞防护 + "-Wl,-z,relro", -- 重定位表只读保护 + "-Wl,-z,now", -- 启动时完成符号绑定 + "-Wl,-z,noexecstack", -- 栈不可执行 + "-Wl,-z,separate-code", -- 代码段与数据段分离 + {force = true} + ) + + add_syslinks("pthread") + + -- 主机侧统一开启 Sanitizer + add_cxflags( + "-fsanitize=alignment", -- 检测未对齐内存访问 + "-fno-sanitize-recover=undefined", -- UB 触发后立即终止 + {force = true} + ) + add_ldflags( + "-fsanitize=alignment", -- 未对齐访问检查 + "-fno-sanitize-recover=undefined", -- UB 触发后立即终止 + "-fsanitize=address", -- 越界/UAF 等内存错误 + "-fsanitize=leak", -- 泄漏检测 + "-fsanitize=undefined", -- 常见未定义行为检测 + "-fsanitize=pointer-compare", -- 非法指针比较 + "-fsanitize=pointer-subtract", -- 非法指针相减 + "-fsanitize=bounds", -- 数组/边界检查 + "-fsanitize=float-divide-by-zero", -- 浮点除零检查 + "-fsanitize=float-cast-overflow", -- 浮点转整型溢出检查 + {force = true} + ) + + -- 编译器警告与静态分析 + add_cxflags( + -- 调试与标准一致性 + "-g3", -- 生成详细调试信息 + "-pedantic", -- 严格遵循标准 + + -- 基础与强化告警 + "-Wall", -- 常用告警集合 + "-Wextra", -- 额外告警 + "-Wconversion", -- 隐式类型转换 + "-Wsign-conversion", -- 有/无符号转换 + "-Wdouble-promotion", -- float 隐式提升为 double + "-Wstrict-prototypes", -- 函数声明原型检查 + "-Wold-style-definition", -- 旧式函数定义检查 + "-Wimplicit-fallthrough", -- switch 穿透检查 + "-Wshadow", -- 变量遮蔽检查 + "-Wcast-align", -- 潜在未对齐转换 + "-Wpointer-arith", -- 指针算术风险 + "-Warray-bounds", -- 数组越界检查 + "-Wshift-overflow", -- 位移溢出检查 + "-Wformat-truncation", -- 格式化截断检查 + "-Walloc-size", -- 分配大小异常 + "-Wnull-dereference", -- 空指针解引用 + "-Wtautological-compare", -- 恒真/恒假比较 + "-Wstrict-overflow", -- 有符号溢出假设 + "-Wmissing-prototypes", -- 缺少原型声明 + "-Wmissing-declarations", -- 缺少对外声明 + "-Wredundant-decls", -- 冗余声明 + "-Wunreachable-code", -- 不可达代码 + "-Wtype-limits", -- 类型边界恒真比较 + "-Wshift-negative-value", -- 负值位移检查 + "-Wdiv-by-zero", -- 除零检查 + "-Wformat-security", -- 格式化安全检查 + "-Wdisabled-optimization",-- 被禁用优化提示 + "-Wreturn-local-addr", -- 返回局部地址检查 + "-Wdeprecated", -- 弃用 API 提示 + "-Wuninitialized", -- 未初始化变量检查 + + -- 代码生成与安全策略 + "-fstack-protector-strong", -- 强栈保护 + "-Wmissing-include-dirs", -- 头文件目录缺失 + "-Wcast-qual", -- 丢失 const/volatile 限定 + "-Wconditional-uninitialized", -- 条件分支未初始化 + "-Wcovered-switch-default", -- switch default 覆盖提示 + "-Wformat-nonliteral", -- 非字面量格式串 + "-Wformat-signedness", -- 格式化符号位不匹配 + "-Wvla", -- 可变长数组检查 + "-fno-common", -- 禁止旧式多重定义 + "-fno-strict-aliasing", -- 放宽别名优化,减少 UB 风险 + "-Wdocumentation", -- 文档注释检查 + "-Wparentheses-equality", -- 可疑括号比较 + + -- 针对当前仓库的降噪项(避免第三方/历史代码噪声淹没关键告警) + "-Wno-documentation", + "-Wno-extra-semi-stmt", + "-Wno-unsafe-buffer-usage", + "-Wno-declaration-after-statement", + "-Wno-padded", + "-Wno-switch-default", + "-Wno-unused-macros", + {force = true} + ) + + -- 头文件 + add_includedirs('./RyanJson', {public = true}) + add_includedirs('./example', {public = true}) + add_includedirs('./test/fuzzer/include', {public = true}) + add_includedirs('./test/unityTest/runner', {public = true}) + add_includedirs('./test/unityTest/common', {public = true}) + add_includedirs('./test/unityTest/include', {public = true}) + add_includedirs('./test/unityTest/cases/core', {public = true}) + add_includedirs('./test/unityTest/cases/equality', {public = true}) + add_includedirs('./test/unityTest/cases/utils', {public = true}) + add_includedirs('./test/externalModule/valloc', {public = true}) + add_includedirs('./test/externalModule/tlsf', {public = true}) + add_includedirs('./test/externalModule/cJSON', {public = true}) + add_includedirs('./test/externalModule/unity/src', {public = true}) + + add_includedirs('./test/unityTest/cases/RFC8259', {public = true}) + -- 头文件仍由主目标暴露,源码改为依赖静态库 yyjsonStatic + add_includedirs('./test/externalModule/yyjson', {public = true}) + -- 依赖第三方静态库(减少重复编译) + add_deps("yyjsonStatic") + + add_includedirs('./test/externalModule/FreeRTOS-Kernel/include', {public = true}) + add_includedirs('./test/externalModule/FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix', {public = true}) + + -- 源文件 + add_files('./RyanJson/*.c', {public = true}) + add_files('./example/*.c', {public = true}) + add_files('./test/unityTest/**.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/list.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/queue.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/tasks.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/timers.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/event_groups.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/stream_buffer.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/port.c', + {public = true}, + {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c', + {public = true}, + {cxflags = "-w"}) + add_files('./test/externalModule/FreeRTOS-Kernel/portable/MemMang/heap_4.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/valloc/*.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/tlsf/*.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/cJSON/*.c', {public = true}, {cxflags = "-w"}) + add_files('./test/externalModule/unity/src/*.c', {public = true}, {cxflags = "-w"}) + + if isFuzz then + -- fuzz 目标:编入全部 fuzz case + add_files('./test/fuzzer/**.c', {public = true}) + else + -- unit 目标:仅补最小 fuzzer runtime 依赖,避免注入 libFuzzer main。 + -- 原因:RyanJsonPrint.c 在 RyanJsonLinuxTestEnv 下会引用 RyanJsonFuzzerShouldFail。 + add_files('./test/fuzzer/utils/fuzzerDriver.c', {public = true}, {cxflags = "-w"}) + add_files('./test/fuzzer/utils/fuzzerMemory.c', {public = true}, {cxflags = "-w"}) + end +end + +target("RyanJson", function() + -- 默认目标:Unit 测试路径(不注入 libFuzzer main) + setupRyanJsonTarget(false) +end) + +target("RyanJsonFuzz", function() + -- 专用 fuzz 目标:默认不参与普通 xmake 构建 + set_default(false) + setupRyanJsonTarget(true) +end) + +-- QEMU Cortex-M 目标:用于硬件语义校验(含非对齐访问 fault) +local function setupRyanJsonQemuTarget(options) + local cpu = options.cpu + local freertosPort = options.freertosPort + local isCm4f = options.isCm4f + + set_kind("binary") + set_default(false) + set_extension(".elf") + + set_toolchains("gcc") + set_plat("cross") + set_arch("arm") + set_languages("gnu99") + set_optimize("fastest") + set_symbols("debug") + + set_toolset("cc", "arm-none-eabi-gcc") + set_toolset("as", "arm-none-eabi-gcc") + set_toolset("ld", "arm-none-eabi-gcc") + set_toolset("ar", "arm-none-eabi-ar") + + local strictObjectKeyCheck = getBooleanEnvDefineValue("RYANJSON_STRICT_OBJECT_KEY_CHECK", "false") + local defaultAddAtHead = getBooleanEnvDefineValue("RYANJSON_DEFAULT_ADD_AT_HEAD", "false") + local snprintfSupportScientific = getBooleanEnvDefineValue("RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC", "true") + + add_defines("RyanJsonStrictObjectKeyCheck=" .. strictObjectKeyCheck) + add_defines("RyanJsonDefaultAddAtHead=" .. defaultAddAtHead) + add_defines("RyanJsonSnprintfSupportScientific=" .. snprintfSupportScientific) + add_defines("RyanJsonTestPlatformQemu") + add_defines("RyanJsonFreeRtosHeap4") + add_defines("RyanJsonProjectRootPath=\"$(projectdir)\"") + add_defines("UNITY_INCLUDE_CONFIG_H") + add_defines("YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS=1") + -- 测试分配模拟参数(可选):用于评估不同 malloc 头部与对齐策略的影响。 + tryAddNumericEnvDefine("RYANJSON_TEST_ALLOC_HEADER_SIZE", "RyanJsonTestAllocHeaderSize", 0) + tryAddNumericEnvDefine("RYANJSON_TEST_ALLOC_ALIGN_SIZE", "RyanJsonTestAllocAlignSize", 1) + + add_cxflags( + "-mcpu=" .. cpu, + "-mthumb", + "-ffunction-sections", + "-fdata-sections", + "-fno-common", + "-fno-strict-aliasing", + "-Wall", + "-Wextra", + "-Wno-unused-parameter", + {force = true} + ) + + if isCm4f then + add_cxflags( + "-mfpu=fpv4-sp-d16", + "-mfloat-abi=hard", + {force = true} + ) + end + + add_asflags( + "-mcpu=" .. cpu, + "-mthumb", + {force = true} + ) + + if isCm4f then + add_asflags( + "-mfpu=fpv4-sp-d16", + "-mfloat-abi=hard", + {force = true} + ) + end + + add_ldflags( + "-mcpu=" .. cpu, + "-mthumb", + "-Ttest/qemu/platform/linkerMps2An386.ld", + "-Wl,--gc-sections", + "-Wl,--print-memory-usage", + "--specs=nosys.specs", + "-nostartfiles", + {force = true} + ) + + if isCm4f then + add_ldflags( + "-mfpu=fpv4-sp-d16", + "-mfloat-abi=hard", + {force = true} + ) + end + + add_syslinks("c", "m", "gcc") + + add_includedirs("./RyanJson", {public = true}) + add_includedirs("./test/qemu/platform", {public = true}) + add_includedirs("./test/qemu/common", {public = true}) + add_includedirs("./example", {public = true}) + add_includedirs("./test/unityTest/runner", {public = true}) + add_includedirs("./test/unityTest/common", {public = true}) + add_includedirs("./test/unityTest/include", {public = true}) + add_includedirs("./test/unityTest/cases/core", {public = true}) + add_includedirs("./test/unityTest/cases/equality", {public = true}) + add_includedirs("./test/unityTest/cases/utils", {public = true}) + add_includedirs("./test/unityTest/cases/RFC8259", {public = true}) + add_includedirs("./test/unityTest/cases/performance", {public = true}) + add_includedirs("./test/externalModule/valloc", {public = true}) + add_includedirs("./test/externalModule/tlsf", {public = true}) + add_includedirs("./test/externalModule/cJSON", {public = true}) + add_includedirs("./test/externalModule/unity/src", {public = true}) + add_includedirs("./test/externalModule/yyjson", {public = true}) + add_includedirs("./test/externalModule/FreeRTOS-Kernel/include", {public = true}) + add_includedirs("./test/externalModule/FreeRTOS-Kernel/portable/GCC/" .. freertosPort, {public = true}) + + add_files("./RyanJson/*.c", {public = true}) + add_files("./example/*.c", {public = true}) + add_files("./test/qemu/platform/qemuPlatform.c", {public = true}) + add_files("./test/qemu/platform/qemuStartup.c", {public = true}) + add_files("./test/qemu/platform/qemuFault.c", {public = true}) + add_files("./test/qemu/platform/qemuSyscalls.c", {public = true}) + add_files("./test/qemu/platform/qemuFreertosHeap.c", {public = true}) + add_files("./test/unityTest/**.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/valloc/*.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/tlsf/*.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/cJSON/*.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/unity/src/*.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/yyjson/*.c", {public = true}, {cxflags = "-w"}) + + add_files("./test/externalModule/FreeRTOS-Kernel/list.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/FreeRTOS-Kernel/queue.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/FreeRTOS-Kernel/tasks.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/FreeRTOS-Kernel/timers.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/FreeRTOS-Kernel/event_groups.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/FreeRTOS-Kernel/stream_buffer.c", {public = true}, {cxflags = "-w"}) + add_files("./test/externalModule/FreeRTOS-Kernel/portable/GCC/" .. freertosPort .. "/port.c", + {public = true}, + {cxflags = "-w"}) + add_files("./test/externalModule/FreeRTOS-Kernel/portable/MemMang/heap_4.c", {public = true}, {cxflags = "-w"}) + + after_build(function(target) + local elfFile = target:targetfile() + local outDir = path.directory(elfFile) + local baseName = path.basename(elfFile) + local binFile = path.join(outDir, baseName .. ".bin") + local hexFile = path.join(outDir, baseName .. ".hex") + + os.execv("arm-none-eabi-objcopy", {"-O", "binary", elfFile, binFile}) + os.execv("arm-none-eabi-objcopy", {"-O", "ihex", elfFile, hexFile}) + end) +end + +target("RyanJsonQemu", function() + setupRyanJsonQemuTarget({ + cpu = "cortex-m4", + freertosPort = "ARM_CM4F", + isCm4f = true, + }) +end) + +target("RyanJsonQemuCm3", function() + setupRyanJsonQemuTarget({ + cpu = "cortex-m3", + freertosPort = "ARM_CM3", + isCm4f = false, + }) + add_defines("RyanJsonQemuSoftUnalignedTrap") +end)