Skip to content

[Test] cover the create() failure path of custom connection - #254

Open
myungjoo wants to merge 1 commit into
nnstreamer:mainfrom
myungjoo:test/issue-253-custom-create-fail
Open

[Test] cover the create() failure path of custom connection#254
myungjoo wants to merge 1 commit into
nnstreamer:mainfrom
myungjoo:test/issue-253-custom-create-fail

Conversation

@myungjoo

@myungjoo myungjoo commented Sep 2, 2026

Copy link
Copy Markdown
Member

Resolves #253.

The gap

nns_edge_custom_load() reaches its error: label in two ways:

  1. _load_custom_library() fails — covered by edgeCustom.createHandleLoadFail_n, added in [Fix] clear out-param when custom lib loading fails #250.
  2. The library loads and exports a valid instance, but that instance's nns_edge_custom_create() returns an error — not covered by anything.

Both are subject to the same "*edge_h must not be left dangling" contract that #250 fixed and documented, so the second path deserves the same regression guard.

Why it could not be tested

tests/nnstreamer-edge-custom-test.c only fails in nns_edge_custom_create() on !priv or OOM, neither of which a test can trigger through the public API. Path (2) therefore needed a failure-injection switch in the custom connection library for test.

What this adds

A control block in the custom connection library for test, exported as nns_edge_custom_test_ctrl and declared in the new tests/nnstreamer-edge-custom-test.h. The unit test resolves it with dlsym() on the very library that nns_edge_custom_load() opens by name, so both refer to one instance regardless of how the loader arranged things:

  • create_error — makes nns_edge_custom_create() return that error without allocating.
  • close_count / close_priv — record how nns_edge_custom_close() was called.

Every member is zero until a test sets it, so the library behaves exactly as before for every other user, and the fixture clears the block in TearDown() so an injected failure cannot leak into another test.

Three tests on top of it:

Test Asserts
edgeCustomFail.createHandleCreateFail_n nns_edge_custom_create_handle() returns the error the custom library raised, and leaves *edge_h == NULL
edgeCustomFail.loadCreateFail_n nns_edge_custom_load() propagates the same error and leaves its own out-param untouched
edgeCustomFail.createHandleAfterCreateFail a failed creation leaves nothing behind; the hooks are inert once the injection is cleared

Verified that the first one is a real guard: temporarily reverting the *edge_h = NULL; line from #250 makes it fail with Value of: edge_h == __null / Actual: false, and restoring it makes it pass.

The close()-after-failed-create() contract

The issue asked to settle this. On path (2) custom->instance is valid, so nns_edge_custom_release() proceeds and calls custom_h->nns_edge_custom_close (custom->priv) on a priv whose create() just failed. Current behavior is kept — it gives a custom library the chance to release whatever it had allocated before failing — and is now documented on the two nns_edge_custom_s members in include/nnstreamer-edge-custom.h, with close_count / close_priv pinning it down in the tests.

No production logic is changed. Restructuring that error path belongs to #252, which rewrites the same error: block.

Scope and impact

  • include/nnstreamer-edge-custom.h — comment only; struct layout, member order and types are untouched, so no API or ABI change.
  • tests/nnstreamer-edge-custom-test.c — shipped only in the unittest sub-package; the new global is zero-initialized, so its behavior is unchanged for existing users.
  • tests/CMakeLists.txt${CMAKE_DL_LIBS} on the custom unittest, which now calls dlopen() itself. It expands to nothing where dl is already in libc.
  • tests/nnstreamer-edge-custom-test.h — test-only, not installed; header installs are listed file by file in src/CMakeLists.txt, and the debian build never sets ENABLE_TEST.
  • src/ — untouched.

CI coverage

The new tests run in both existing paths, so a regression cannot merge: ctest in the ubuntu cmake workflow (registered via ADD_TEST with the LD_LIBRARY_PATH property from #251) and run_unittests.sh ./tests/unittest_nnstreamer-edge-custom in the GBS build.

Locally verified on Ubuntu 22.04 / gcc 11 / gtest 1.12.1: unittest_nnstreamer-edge-custom 29/29 and unittest_nnstreamer-edge 171/171 pass, ctest reports 2/2, the build is free of warnings under -Wall -Werror, clang-format reports no diff, and flawfinder --minlevel=1 reports no hits on either changed C/C++ file.

🤖 Generated with Claude Code

@myungjoo myungjoo added the DONOTMERGE Do not merge yet label Sep 2, 2026
@myungjoo

myungjoo commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Note: This review was produced by an automated code-review agent and is being transcribed here verbatim on the requester's behalf; it is not a manual human review.

Summary

PR #254 fully addresses issue #253's suggested scope: it adds failure injection to tests/nnstreamer-edge-custom-test.c for the nns_edge_custom_create() failure path, adds negative tests for both nns_edge_custom_create_handle() and nns_edge_custom_load() on that path, and documents the close()-after-failed-create() contract in include/nnstreamer-edge-custom.h. All three items requested in the issue are covered.

Verification performed

  • Cross-checked the new tests against the actual production code path in src/libnnstreamer-edge/nnstreamer-edge-custom-impl.c (nns_edge_custom_load / nns_edge_custom_release) and src/libnnstreamer-edge/nnstreamer-edge-internal.c (nns_edge_custom_create_handle). The described control flow is accurate: when nns_edge_custom_create() fails, custom->priv is still NULL (from calloc) unless create() set it, nns_edge_custom_release() is invoked internally by nns_edge_custom_load()'s error path (calling close() once with that NULL priv), and nns_edge_custom_create_handle() resets *edge_h = NULL per the [Fix] clear out-param when custom lib loading fails #250 contract. The new close_count == 1 / close_priv == NULL assertions match this exactly.
  • Confirmed include/nnstreamer-edge-custom.h is a publicly installed header (src/CMakeLists.txt, packaging/nnstreamer-edge.spec) and that the change is comment-only — struct layout/order/types are untouched, so there is no ABI/API break and no further doc updates are needed.
  • Confirmed tests/nnstreamer-edge-custom-test.h is test-only and not installed.
  • Confirmed the new tests run in both CI paths mentioned in the PR description: ADD_TEST(NAME unittest_nnstreamer-edge-custom ...) in tests/CMakeLists.txt (exercised by .github/workflows/ubuntu_clean_cmake_build.yml via ctest), and run_unittests.sh ./tests/unittest_nnstreamer-edge-custom in packaging/nnstreamer-edge.spec, both gated behind ENABLE_CUSTOM_CONNECTION as before.
  • ${CMAKE_DL_LIBS} addition is correctly scoped to the one target that now calls dlopen()/dlsym() directly in test code; the project already depends on dlfcn.h unconditionally elsewhere (nnstreamer-edge-custom-impl.c), so this introduces no new platform constraint.
  • The dlopen-refcount design (test fixture dlopens the same .so the API under test also dlopens by name, relying on POSIX dlopen returning/refcounting the same mapped object) is sound, and SetUp/TearDown correctly zero the control block so an injected failure cannot leak into other tests.
  • No changes under src/; diff is tightly scoped to the stated goal (163 insertions / 1 deletion across a header comment, a build file, a small test-support header/source, and the test binary).

Blocking issues

None found.

Non-blocking nits

  1. Global test-control state (nns_edge_custom_test_ctrl) is not synchronized. It's a plain global struct written from nns_edge_custom_close/_create and read/reset from the gtest fixture. This is fine as long as unittest_nnstreamer-edge-custom always runs single-threaded/non-sharded (true today), but it's an implicit assumption worth a one-line comment if the test binary is ever run with parallel gtest sharding in the future.
  2. The PR description's claim that "no production logic is changed" is correct, and worth calling out for reviewers who skim only the diff: the only touched file in the shipped library is a comment-only header edit.

Conclusion

Approve. The change is well-scoped, accurately verified against the real error path it targets (including a documented before/after check that reverting the #250 *edge_h = NULL fix makes the new negative test fail), does not touch src/, does not risk regressions in other modules, and is wired into both CI paths so a future regression on this path would fail the build. No architecture or public API change occurred beyond documentation, so no further doc updates are required.

@myungjoo
myungjoo force-pushed the test/issue-253-custom-create-fail branch from 76b8e1c to b2182a0 Compare September 2, 2026 02:37
@myungjoo

myungjoo commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Addressed nit 1 in b2182a0: tests/nnstreamer-edge-custom-test.h now states that the control block is not synchronized and that a test drives one connection from one thread, so the assumption is explicit for anyone who later runs this binary differently.

Nit 2 needs no change — the PR description already states that src/ is untouched and that the only shipped-library edit is comment-only.

No other change; re-running the review on the updated commit.

@myungjoo

myungjoo commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Note: This review was produced by an automated code-review agent and is being transcribed here verbatim on the requester's behalf; it is not a manual human review.

Scope

Re-review of PR #254 at the current head, commit b2182a0, following up on the review at #254 (comment). The PR is a single commit (b2182a0) on top of main; its diff is unchanged in substance from what the prior review examined, with one addition: the "not synchronized" caveat requested as nit 1.

Verification of the prior nit

Nit 1 ("the global test-control state is not synchronized — worth a one-line comment") is resolved. tests/nnstreamer-edge-custom-test.h now documents this explicitly on nns_edge_custom_test_ctrl_s:

"The block is not synchronized, as a test drives one connection from one thread."

This is accurate and sufficient — unittest_nnstreamer-edge-custom runs gtest tests sequentially in one process, matching the assumption.

Independent verification performed

  • Re-diffed upstream/main..pr-254 directly (164 insertions / 1 deletion across 5 files: include/nnstreamer-edge-custom.h, tests/CMakeLists.txt, tests/nnstreamer-edge-custom-test.c, new tests/nnstreamer-edge-custom-test.h, tests/unittest_nnstreamer-edge-custom.cc). No src/ files are touched.
  • Re-read src/libnnstreamer-edge/nnstreamer-edge-custom-impl.c (nns_edge_custom_load, nns_edge_custom_release) and src/libnnstreamer-edge/nnstreamer-edge-internal.c (nns_edge_custom_create_handle, nns_edge_release_handle) on main. Confirmed the described control flow holds: on a create() failure, custom->priv stays whatever create() left it as (NULL here, since the test library's create() returns before calloc), nns_edge_custom_load's error: path calls nns_edge_custom_release() which calls close(priv) once, and nns_edge_custom_create_handle resets *edge_h = NULL per the [Fix] clear out-param when custom lib loading fails #250 contract. The new close_count == 1 / close_priv == NULL assertions match this exactly.
  • Traced the dlopen/dlclose refcounting across all three new tests: the fixture's own dlopen() in SetUp() holds a reference for the whole test, so the ctrl block stays mapped even as production code's own dlopen()/dlclose() calls (via nns_edge_custom_load/nns_edge_custom_release) run and unwind within the same test. Confirmed nns_edge_release_handle() does route NNS_EDGE_CONNECT_TYPE_CUSTOM handles through nns_edge_custom_release(), so the second (successful) create_handle call in createHandleAfterCreateFail is symmetrically opened and closed. No use-after-unmap or refcount imbalance.
  • Confirmed CI wiring: ENABLE_CUSTOM_CONNECTION defaults ON in the top-level CMakeLists.txt, so .github/workflows/ubuntu_clean_cmake_build.yml's cmake -DENABLE_TEST=ON + ctest actually builds and runs unittest_nnstreamer-edge-custom (the workflow doesn't pass the flag explicitly, but the default covers it). tests/CMakeLists.txt's SET_TESTS_PROPERTIES(... ENVIRONMENT "LD_LIBRARY_PATH=...") covers both the production dlopen() and the new fixture's own dlopen(). Confirmed the packaging spec (packaging/nnstreamer-edge.spec) separately runs LD_LIBRARY_PATH=./src:./tests bash run_unittests.sh ./tests/unittest_nnstreamer-edge-custom for the GBS/Tizen path. Both paths would fail on a regression.
  • Confirmed tests/nnstreamer-edge-custom-test.h is not installed anywhere in the spec's %files sections (only include/nnstreamer-edge-custom.h is, under %files devel), matching the PR's claim that it's test-only.
  • Ran clang-format -style=file (repo's .clang-format, clang-format 16) against the modified tests/unittest_nnstreamer-edge-custom.cc — zero diff, so the CI clang-format gate will pass.
  • Checked the modified tests/nnstreamer-edge-custom-test.c against surrounding style (2-space indent, unbraced single-statement if) — consistent with existing code in the same file and with the same pattern used throughout src/libnnstreamer-edge/nnstreamer-edge-custom-impl.c, so no GNU-indent CI failure is expected (could not run indent itself locally to confirm byte-for-byte, since it isn't installed in this environment).
  • Checked exported-symbol visibility: no -fvisibility=hidden in CMakeLists.txt (only -fPIC -fPIE), so the new non-static nns_edge_custom_test_ctrl global is dlsym-resolvable by default, consistent with the existing nns_edge_custom_get_instance pattern the same library already relies on.
  • Test naming follows the file's existing convention: _n suffix on the two pure-negative tests, no suffix on createHandleAfterCreateFail since it ends in a successful create/release, matching edgeCustom.createHandle (no suffix) vs. edgeCustom.createHandleLoadFail_n (_n suffix).
  • No architecture or public API/ABI change: include/nnstreamer-edge-custom.h's edit is comment-only, struct layout/order/types untouched. No other doc file in the repo references nns_edge_custom_create/nns_edge_custom_close, so the header comment is the correct and complete place for this documentation.

Blocking issues

None.

Non-blocking nits

  1. edgeCustomFail.createHandleAfterCreateFail doesn't assert close_count/edge_h == NULL after the first (failing) create_handle call the way the other two negative tests do — it only checks ret != NNS_EDGE_ERROR_NONE before moving on to the successful retry. That's fine since the other two tests already cover those assertions directly, but adding them here too would make this test independently self-checking rather than relying on test-order coverage from its siblings. Optional.

Conclusion

Approve. This re-review at b2182a0 confirms the previously-requested nit (documenting that the test control block is unsynchronized and single-threaded) was correctly addressed, and independently re-verifies the production control-flow claims, the CI/packaging wiring on both the Ubuntu-cmake and GBS paths, and clang-format compliance. The change remains tightly scoped (no src/ edits, comment-only public header change, test-only additions), and does not introduce any regression risk to other modules.

@myungjoo

myungjoo commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Addressed the new nit: edgeCustomFail.createHandleAfterCreateFail now asserts its own premise before testing recovery — edge_h == NULL and close_count == 1 after the injected failure — so the test can no longer pass on a first call that silently did not fail the way it is meant to.

@myungjoo
myungjoo force-pushed the test/issue-253-custom-create-fail branch from b2182a0 to f6788ee Compare September 2, 2026 02:42
@myungjoo

myungjoo commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Note: This review was produced by an automated code-review agent and is being transcribed here verbatim on the requester's behalf; it is not a manual human review.

Scope

Re-review of PR #254 at the current head, commit f6788ee, following up on the two prior reviews at #254 (comment) and #254 (comment) (both against b2182a0).

Delta since the last review (b2182a0f6788ee)

Diffed the two commits directly: the only change is in tests/unittest_nnstreamer-edge-custom.cc, inside edgeCustomFail.createHandleAfterCreateFail:

   ret = nns_edge_custom_create_handle ("temp-id", "libnnstreamer-edge-custom-test.so",
       NNS_EDGE_NODE_TYPE_QUERY_SERVER, &edge_h);
   ASSERT_NE (NNS_EDGE_ERROR_NONE, ret);
+  ASSERT_TRUE (edge_h == NULL);
+  ASSERT_EQ (1, ctrl->close_count);

This is exactly the nit raised in the second review ("this test doesn't assert close_count/edge_h == NULL after the first failing call, so it silently relies on its siblings for that coverage"). It is now self-checking: the test can no longer pass on a first call that didn't actually fail the way it's meant to. No other file changed.

Verification performed on this pass

  • Re-fetched nnstreamer/nnstreamer-edge's real main (56cc280, the PR's actual baseRefOid) and confirmed gh pr diff 254 is byte-identical to git diff 56cc280..f6788ee — the PR contains exactly the 5-file, 166/-1 diff it claims (include/nnstreamer-edge-custom.h, tests/CMakeLists.txt, tests/nnstreamer-edge-custom-test.c, new tests/nnstreamer-edge-custom-test.h, tests/unittest_nnstreamer-edge-custom.cc). No src/ files touched.
  • Re-read nns_edge_custom_load() / nns_edge_custom_release() in src/libnnstreamer-edge/nnstreamer-edge-custom-impl.c and nns_edge_custom_create_handle() in src/libnnstreamer-edge/nnstreamer-edge-internal.c at the PR base. Independently confirmed the exact control flow the new tests assert on:
    • On a create() failure, ret from create() is preserved through the error: label; nns_edge_custom_release(custom)'s own return value is discarded there, so the propagated error is always the one create() raised, never whatever close() happens to return (e.g. NNS_EDGE_ERROR_INVALID_PARAMETER on a NULL priv). This matches loadCreateFail_n's EXPECT_EQ (NNS_EDGE_ERROR_CONNECTION_FAILURE, ret).
    • nns_edge_custom_load() never writes *handle on the failure path, so handle/eh->custom_connection_h stays NULL — matching EXPECT_TRUE (handle == NULL) and the new ASSERT_TRUE (edge_h == NULL).
    • nns_edge_custom_create_handle() calls nns_edge_release_handle(eh) once and sets *edge_h = NULL on failure; since eh->custom_connection_h is NULL at that point, the release path does not call nns_edge_custom_close() a second time — matching ASSERT_EQ (1, ctrl->close_count).
    • All of this is consistent with the pre-existing *edge_h = NULL contract from [Fix] clear out-param when custom lib loading fails #250 that this PR is explicitly extending coverage for.
  • Confirmed no markdown/doc files in the repo reference nns_edge_custom_create/nns_edge_custom_close, so the header-comment documentation added in include/nnstreamer-edge-custom.h (comment-only, struct layout/order/types untouched — no ABI/API break) remains the correct and complete place for it.
  • Confirmed tests/CMakeLists.txt's ${CMAKE_DL_LIBS} addition is scoped to the one target (unittest_nnstreamer-edge-custom) that now calls dlopen()/dlsym() directly, and that the custom-test shared library is still built and wired into ADD_TEST exactly as before.
  • No build toolchain (cmake/gcc/dlfcn.h) is available in this review environment to reproduce the 29/29 / 171/171 / ctest 2/2 run claimed in the PR description; this pass relies on static/control-flow verification only, same as the diff analysis above.

Blocking issues

None.

Non-blocking / optional nit (new observation, not previously raised)

nns_edge_custom_test_ctrl.close_priv = priv; in tests/nnstreamer-edge-custom-test.c stores a void * that the same function frees a few lines later (SAFE_FREE (custom_h)), and the test fixture only ever compares it for identity (== NULL / != NULL), never dereferences it. That's safe in practice, but it's a pattern not used anywhere else in this codebase (src/ never stores a soon-to-be-freed pointer in a struct field). Given the project already carries a "fix svace issue" commit in this branch's history, a strict static analyzer could flag a dangling pointer being copied into a global. A one-line comment noting the pointer is an opaque token used for identity comparison only (never dereferenced) would preempt that. Purely optional — does not block merge.

Conclusion

Approve. The only change since the last reviewed commit (b2182a0) is the two additional assertions requested in the prior review's sole open nit, and it is applied correctly and exactly as promised. Independent re-verification of the production control-flow claims against the real main base still holds. The change remains tightly scoped (no src/ edits, comment-only public header change, test-only additions), is wired into both CI paths (ctest via the ubuntu-cmake workflow, run_unittests.sh in the GBS/Tizen packaging spec) so a future regression on this path would fail the build, and introduces no architecture or public API/ABI change requiring further documentation.

nns_edge_custom_load() reaches its error path in two ways: the library
cannot be loaded, or the loaded library's nns_edge_custom_create()
returns an error. Only the first was covered, by
edgeCustom.createHandleLoadFail_n added for issue nnstreamer#249, even though
both are governed by the same "*edge_h must not be left dangling"
contract.

The second path could not be reached from a test because the custom
connection library for test only fails on !priv or OOM. Add a control
block to it, exported as nns_edge_custom_test_ctrl and resolved by the
test with dlsym() on the very library that nns_edge_custom_load()
opens by name, so both refer to one instance:
- create_error makes nns_edge_custom_create() fail on demand.
- close_count and close_had_priv record how nns_edge_custom_close()
  was called, which pins down what a custom library gets after its own
  create() has failed.

Every member is zero until a test sets it, so the library keeps its
former behavior for every other user, and the test fixture clears the
block in TearDown() so an injected failure cannot leak into another
test.

Add three tests on top of that:
- createHandleCreateFail_n asserts nns_edge_custom_create_handle()
  returns the error the custom library raised and leaves *edge_h NULL.
  Reverting the issue nnstreamer#249 fix makes it fail.
- loadCreateFail_n asserts nns_edge_custom_load() propagates the same
  error and leaves its own out-param untouched.
- createHandleAfterCreateFail asserts a failed creation leaves nothing
  behind, so the hooks stay inert once the injection is cleared.

Document the create()/close() contract that these tests pin down in
nnstreamer-edge-custom.h: close() is invoked even when create() fails,
and it then receives whatever create() left in priv.

Link ${CMAKE_DL_LIBS} into the custom unittest, which now calls
dlopen() itself.

Fixes nnstreamer#253

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
@myungjoo

myungjoo commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Addressed the close_priv nit in fda8f91, by removing the stored pointer rather than annotating it.

nns_edge_custom_close() frees priv a few lines after recording it, so the member held an indeterminate value by the time a test compared it — formally undefined, even though the tests only ever did identity comparisons. The member is now int close_had_priv, set from (NULL != priv) at the moment close() is entered, and the three assertions compare against 0 or 1. Nothing dangling is kept, so there is nothing left for a static analyzer to flag.

@myungjoo
myungjoo force-pushed the test/issue-253-custom-create-fail branch from f6788ee to fda8f91 Compare September 2, 2026 02:47
@myungjoo

myungjoo commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Note: This review was produced by an automated code-review agent and is being transcribed here verbatim on the requester's behalf; it is not a manual human review.

Scope

Re-review of PR #254 at the current head, commit fda8f91, following up on the three prior reviews (#254 (comment), #issuecomment-5503526234, #issuecomment-5503561237). This pass focuses only on the f6788eefda8f91 delta and whether it settles the sole open item from the last review: the close_priv nit.

Delta since the last review (f6788eefda8f91)

 tests/nnstreamer-edge-custom-test.c      | 2 +-
 tests/nnstreamer-edge-custom-test.h      | 2 +-
 tests/unittest_nnstreamer-edge-custom.cc | 6 +++---
 3 files changed, 5 insertions(+), 5 deletions(-)

The only change is renaming/retyping one control-block member:

  • tests/nnstreamer-edge-custom-test.h: void *close_privint close_had_priv, doc comment updated to "Whether the last nns_edge_custom_close() call got a non-NULL priv."
  • tests/nnstreamer-edge-custom-test.c: nns_edge_custom_close() now sets close_had_priv = (NULL != priv) at entry, instead of storing the raw priv pointer that the same function frees a few lines later.
  • tests/unittest_nnstreamer-edge-custom.cc: the three assertions in createHandleCreateFail_n, loadCreateFail_n, createHandleAfterCreateFail switch from EXPECT_TRUE (ctrl->close_priv == /!= NULL) to EXPECT_EQ (0/1, ctrl->close_had_priv).

No other file changed; src/ remains untouched.

Verification of the close_priv fix

The prior review's nit was that close_priv stored a void * copy of priv right before SAFE_FREE (custom_h) freed it, leaving a formally-indeterminate (freed) pointer sitting in a global struct — the exact shape a strict static analyzer (the PR's own history includes a "fix svace issue" commit) tends to flag, even though the tests only ever did identity comparisons on it and never dereferenced it.

Confirmed the fix removes the root cause rather than annotating around it: close_had_priv is computed as a boolean before the free, so no pointer value — dangling or otherwise — is ever stored anywhere. grep across the tree confirms no remaining reference to close_priv in any .c/.cc/.h file. This is a stronger fix than the "add a comment noting it's an opaque identity token" suggestion the nit itself floated, and it fully addresses the concern.

Checked the three updated assertions individually against the code path they guard (createHandleCreateFail_n / loadCreateFail_n assert close_had_priv == 0 on the injected-create-failure path, where priv is never allocated so nns_edge_custom_close() is called with priv == NULL; createHandleAfterCreateFail asserts close_had_priv == 1 after a real, successful creation is released) — the boolean each asserts matches the boolean the corresponding EXPECT_TRUE/==/!= NULL check asserted before, so no test coverage was lost in the rewrite, and close_count assertions (unchanged) still pin down call counts as before.

Blocking issues

None.

Non-blocking nits

None new. The close_priv nit from the prior review is resolved, and no other issue surfaced in this delta.

Conclusion

Approve. The f6788eefda8f91 delta is a minimal, correctly-scoped fix that eliminates (rather than papers over) the only open nit from the previous review — no dangling/freed pointer is stored anywhere in the test control block now. No production code, build wiring, or other test assertions were touched beyond what the fix required. Nothing further to raise.

@myungjoo myungjoo removed the DONOTMERGE Do not merge yet label Sep 2, 2026
@myungjoo
myungjoo marked this pull request as ready for review September 2, 2026 03:17

@myungjoo-bot myungjoo-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this review was produced by an automated review agent and is transcribed here by the bot account.

이 리뷰는 자동 코드 리뷰 에이전트(github-code-review 스킬)가 작성한 결과를 봇 계정이 옮겨 적은 것입니다.

fda8f91c 기준 리뷰입니다. 코드 자체에는 막을 만한 문제가 없습니다. 다만 아래 1번(#255와의 중복·충돌) 때문에 approve 대신 comment로 남깁니다 — 어느 쪽을 먼저 태울지는 메인테이너 판단이 필요합니다.

리뷰 범위

include/nnstreamer-edge-custom.h (+11, 주석만), tests/CMakeLists.txt (+1/-1), tests/nnstreamer-edge-custom-test.c (+9), tests/nnstreamer-edge-custom-test.h (신규 42줄), tests/unittest_nnstreamer-edge-custom.cc (+103).

확인한 것

  • 주제 해결: nns_edge_custom_load()error: 라벨에 이르는 두 경로 중 create() 실패 쪽이 미커버라는 진단이 맞고, 테스트용 커스텀 라이브러리에 실패 주입 스위치를 넣지 않고서는 공개 API로 그 경로를 밟을 수 없다는 설명도 코드상 정확합니다(기존 nns_edge_custom_create()!priv와 OOM에서만 실패).
  • 제어 블록 공유가 실제로 성립합니다: fixture가 dlopen("libnnstreamer-edge-custom-test.so")로 잡은 인스턴스와 nns_edge_custom_load()가 같은 soname으로 여는 인스턴스는 동일 매핑이므로 dlsym()으로 얻은 포인터가 유효합니다. 게다가 fixture가 테스트 내내 참조를 하나 붙들고 있어, 실패 경로에서 _load_custom_library()dlclose()를 해도 제어 블록이 언매핑되지 않습니다. 주석에 적힌 의도대로 동작합니다.
  • 격리: SetUp()/TearDown() 양쪽에서 제어 블록을 0으로 지우므로 주입된 실패가 다른 테스트로 새지 않습니다. SetUp()의 fatal 단언으로 본문이 스킵되어도 TearDown()이 NULL 가드와 함께 돌아 안전합니다. 환경변수 방식보다 구조적으로 견고합니다.
  • 단언의 정확성: createHandleCreateFail_n이 기대하는 close_count == 1, close_had_priv == 0은 현재 구현과 일치합니다 — create()가 할당 없이 실패하므로 custom->priv가 NULL인 채 nns_edge_custom_release()close(NULL)을 한 번 호출합니다. *edge_h == NULL 단언은 #250이 넣은 라인을 되돌리면 실제로 깨진다는 저자 검증도 타당합니다(그 라인이 유일한 대입 지점입니다).
  • 회귀 위험: src/ 무변경, 공개 헤더는 주석만(멤버 순서·타입·레이아웃 그대로라 ABI 영향 없음), 제어 블록은 zero-initialized라 스위치를 켜지 않는 기존 사용자에게 동작 변화가 없습니다. ${CMAKE_DL_LIBS} 추가는 테스트가 직접 dlopen/dlsym을 쓰게 된 데 대한 정확한 대응이며 glibc에서는 빈 문자열로 전개됩니다.
  • CI: 새 테스트는 ubuntu cmake의 ctest(#251에서 등록)와 GBS의 run_unittests.sh ./tests/unittest_nnstreamer-edge-custom 양쪽에서 돌아 회귀를 막을 수 있습니다. 전 항목 green이고, 새 헤더도 doxygen/indent/newline 등 static check를 통과했습니다.
  • 보안: 백도어로 볼 만한 코드 없음. 실패 주입 심볼은 설치되지 않는 테스트 전용 헤더로 선언되고, 노출되는 전역은 unittest 서브패키지에만 들어가는 테스트 라이브러리에 있습니다.

지적 사항

  1. #255와 중복이고 텍스트 충돌합니다 (blocking). #255가 #252(릭)와 #253(이 PR이 닫으려는 이슈)을 함께 닫으면서, create() 실패 경로 테스트를 환경변수 스위치 방식으로 이미 넣고 있습니다. 두 head를 로컬에서 merge해 본 결과 tests/nnstreamer-edge-custom-test.ctests/unittest_nnstreamer-edge-custom.cc가 CONFLICT입니다(공개 헤더의 계약 주석도 양쪽이 각각 추가). 어느 쪽이 먼저 들어가든 나머지 하나는 손봐야 합니다.
    권하는 순서는 릭 수정이 들어 있는 #255를 먼저 머지하고, 이 PR은 #255가 갖지 못한 부분만 남겨 리베이스하는 것입니다:
    • fixture + 제어 블록 방식(환경변수 전역 상태를 쓰지 않고, TearDown()이 항상 정리)
    • close_count / close_had_priv"create() 실패 후 close()가 어떻게 불렸는지"를 관측하는 부분 — 두 PR이 문서로만 적어 둔 계약을 실제로 고정하는 것은 이 PR뿐입니다.
      반대로 이 PR을 먼저 태우기로 한다면, #255에서 중복되는 createHandleCreateFail_n/loadCreateFail_n과 환경변수 스위치를 걷어내는 편이 깔끔합니다.
  2. (사소) PR 본문의 표에는 close_count / close_priv로 적혀 있는데 실제 멤버는 close_had_priv입니다. 코드가 아니라 설명 쪽 오타입니다.
  3. (사소) create_error가 켜진 상태의 nns_edge_custom_create()!priv 검증보다 먼저 반환합니다. 이 파일은 공개 헤더가 예제로 가리키는 참조 구현이니, 주입 검사를 if (!priv) 뒤로 내려 파라미터 검증 순서를 보존하는 편이 낫습니다. (#255의 환경변수 스위치도 같은 위치에 있어 동일한 지적을 남겼습니다.)

테스트 설계와 구현 자체는 견고합니다. 1번의 중복만 정리되면 approve할 수 있습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No test coverage for the nns_edge_custom_create() failure path in nns_edge_custom_load()

2 participants