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.

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)

2 answers

+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)
+1
−0

I'd propose (and have generally used, back when I worked on C projects) a fourth option that I don't think gets too far out of line. If I understand the situation correctly, that you want to test how an "object" reacts under a barrage of test conditions, independent of the rest of the software, then you would probably have a better time writing a dedicated "test harness" for that object, stubbing out everything that it depends on and driving it with the code that you'd otherwise represent in option #3's header file.

This avoids the problem with the "include everything as a header" approach that violates the spirit of how things work, even if compilers find it permissible. It doesn't risk shipping code accidentally, as you might with conditional directives, because those harnesses should live external to the main project and don't interact with the rest of the code. And it gives you more control and a more complete example than just a header file.

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

0 comment threads

Sign up to answer this question »