Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on Testing an opaque type's internals

Parent

Testing an opaque type's internals

+5
−0

First: What is an opaque pointer in C?

Now when it comes to testing such a type, I know of 3 ways:

  1. Include the source file (the one containing the definition of the type and the functions that work with it) directory into the test source file. (This is the easiest, but often discouraged without presenting any rationale).

  2. Make public accessors that are conditionally available (i.e. only if if LIB_TEST is defined before including its header).

  3. Make a separate "lib-test.h" header file that contains the public accessors.

The last two avoid making the accessors part of the public API (we're speaking of the case where the clients have no business knowing anything about the opaque type's internals, and shouldn't be provided with any accessors).

What is the usual approach (or the good approach) in C? Does one way have more upsides/downsides than the other?

As an example, say I have this struct:

typedef struct arena {
    size_t count;
    size_t capacity;
    size_t current;
    size_t last_alloc_size;
    M_Pool *pools[];
} Arena;

and a function associated with it:

void arena_reset(Arena *arena)
{
    for (size_t i = 0; i < arena->count; ++i) {
        arena->pools[i]->offset = 0;
    }
    arena->current = 1;
}

Now I wish to assert that arena_reset() actually has set the offset field of all the arena's pools to 0, and the arena's current field to 1. Including the API (header file) that this library provides is of no good, because only a forward declaration of the struct type has been provided, and its members can not be accessed.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

1 comment thread

typedef struct (1 comment)
Post
+3
−0
  • "Black box testing" makes sense when dealing with opaque types so that's the first thing you should be doing and probably the most meaningful test too, so that's where you should put most of your efforts. But that's not what you are asking about here.

  • Some philosophies like TDD would encourage you to write the test along with the code and include it in the source from scratch. The advantage is that you'll be writing the test while you are writing the code, allowing you to spot bugs early on and letting you write the test while everything is still fresh.

    The disadvantage is that it's bad practice to keep source around which isn't executed in the production build. That practice is banned in mission-critical software. Similarly, using #ifdef switches is also bad practice since it makes the code harder to read and maintain.

  • Testing the internals could otherwise be done by creating a test branch and modifying the code there to include the tests. It's kind of similar to including the .c file or moving the struct definition to a header during the test, all are just different flavours of the same thing. Either way the original file is untouched, though you need to ensure that your test code doesn't have any impact on it. Exposing the internals could lead to namespace collisions etc.

  • There's no need to overthink testing and be hellbent on automated testing with output through stdout etc. You can just write a manual test such as:

    arena.c, test n.n

    Purpose: Ensure that arena_reset clears all the memory pool.
    Method: Launch the program in debugger x with build settings y. Set a breakpoint at the end of arena_reset. Watch arena->pools, all offset members must be set to zero. Watch arena->current, verify that it is 1.

    This is perfectly fine, just document the test in a protocol. When the test was done, for what build and what was the outcome. If someone thinks this is more cumbersome and takes more time than automated tests, they are fooling themselves.

  • If you know the size of the struct you could just dump n bytes of the memory starting from the opaque pointer and reverse engineer it from there. Cumbersome but might expose security issues because this is something that the user of your code could as well be doing too.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

1 comment thread

You should motivate "it's bad practice to keep source around which isn't executed in the production b... (2 comments)
You should motivate "it's bad practice to keep source around which isn't executed in the production b...
Jacob Raihle‭ wrote 7 months ago

You should motivate "it's bad practice to keep source around which isn't executed in the production build ..." - otherwise it is just another instance of "discouraged without presenting any rationale". Many, many production systems have "debug" modes, and an absence of tests also makes code harder to maintain.

Lundin‭ wrote 7 months ago · edited 7 months ago

Jacob Raihle‭ This is standard practice for critical systems. See for example MISRA C:2023 chapter 8.2 complete with sources and rationales - this in turn is derived from safety standards like IEC 61508 and DO-178. Now of course most code out there isn't mission-critical, but when you look at these standards, they really say between the lines that "bugs are unacceptable". Whereas non-critical systems may have policies between the lines that say "bugs are fine, just ship it". In the end, it's just a matter of how strict your development procedure is. But bad practices are bad practices no matter if you tolerate some amount of bugs or not.