Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #832, add "handler" feature to utassert stub API #966

Conversation

jphickey
Copy link
Contributor

Describe the contribution
Adds the concept of a "handler" function to UT assert. A handler is basically the custom logic that exists between the hook function and the return to the stub caller. In current UT stubs, this is hard coded, and it generally comprises setting output parameters and translating return values as needed.

This concept adds the basic plumbing to allow the handler to be configured just like a hook function already does. The difference
is that the handler is directly responsible for setting all outputs.

This also includes a script to auto-generate stub functions that match this pattern. Given an API header file, the script extracts
the declarations, and generates a source file with stub definitionsthat rely on a separate handler to deal with the needed outputs.

Lastly, all existing stubs in OSAL will be updated to use the auto-generated stub logic from the script, created directly from the C header. This ensures that stubs will match the FSW implementation. Note - one header in FSW (os-shared-printf.h) was broken into two parts, to improve the ability to do this.

Fixes #832

Testing performed
Build and sanity check CFE
Run all unit tests, ensure all tests pass. Confirm that unmodified coverage test scripts are passing as-is, and coverage is still 100%.

Expected behavior changes
This change only affects coverage testing with stubs, and should be fully backward compatible with existing tests.
Once a stub is regenerated via the auto-generator script:

  • Stub is exactly in sync with FSW implementation
  • any return type handled (not just int32)
  • all args passed through context
  • handler can be completely overridden by test case when desired, such that test case can have 100% control over the stub call.

System(s) tested on
Ubuntu 20.04
RTEMS 4.11

Additional context
The "handler" function uses a different hook function prototype than the hook functions do. The latter is kept the same for backward compatibility with existing tests.

The intent is to address a few long-standing shortcomings in the design of the hook function. In particular, hook function design assumes/requires that the function returns int32. While most do, some do not, and this is a major issue if the goal is to completely replace the handler associated with a stub.

In this design there is now a dedicated data buffer to hold the return value, which can be of any data type, and a macro that the handler (or hook) implementation can use to fill/set that return value. However the framework will automatically populate that buffer from the int32 status code if the hook/handler did not populate it, which was what ~90% of stub functions did in their "post-hook" logic. This simplifies things in that those functions don't need any handler at all unless they had other work to do.

Contributor Info - All information REQUIRED for consideration of pull request
Joseph Hickey, Vantage Systems, Inc.

@jphickey jphickey force-pushed the fix-832-stub-handler-replacements branch 2 times, most recently from a76d7ac to 7757078 Compare April 16, 2021 15:18
@jphickey jphickey added the CCB:Ready Pull request is ready for discussion at the Configuration Control Board (CCB) label Apr 16, 2021
@jphickey jphickey requested a review from asgibson April 16, 2021 15:23
@jphickey
Copy link
Contributor Author

This can be reviewed now. For review purposes I split this into multiple commits. The main API addition is in the first commit, which only affects a few files. This should be the first focus of review.

The second commit is the only one that touched FSW, and the goal was to split up "Impl" headers where they were grouped in a problematic way for scripting. It just splits a header file into two.

The remainder of commits focus on fixing/updating all the existing stubs to use this new pattern. Existing "post-hook" logic from the original stub is moved to a default handler function, and the stub itself is now generated directly from the C header using the generator script.

Note that no part of this PR changes a single test case. All changes are transparent to existing tests (migrating the "special sauce" from being in the stub itself into a default handler preserves all existing behavior so all tests still work as they always have).

@jphickey
Copy link
Contributor Author

Slight addendum to my above statement about not affecting existing tests - there was a direct ref to the "ArgPtr" member of the context in the sample_lib test, which actually did break because auto-generated stub registers this differently. See nasa/sample_lib#60

In reality all hooks should use the provided macro - it is much more robust for several reasons.

@asgibson
Copy link
Contributor

There is a bit to get through here, but I will start on it; reviewing the stub generation is where I will begin.

I do have a couple questions about the Testing performed

Confirm that unmodified coverage test scripts are passing as-is, and coverage is still 100%.

Is that after the change is implemented or before? Were any tests performed to show use cases where the stub logic is completely replaced? I'm sure I will get there at some point, just curious if there are any current results.

@jphickey
Copy link
Contributor Author

Is that after the change is implemented or before

Both. This is one reason I pushed as separate commits. This way the first commit would also individually have CI run against it to get the green check mark, showing that the first commit alone does pass CI.

Basically you can take only the first commit and run all tests, they still pass, and still show same coverage level. (i.e. with only the new stub API and internal utassert stub impl changes added, but no actual stub changes, nothing breaks). You can then add the remainder of commits which update all the OSAL stubs to use the generated/split (stub+default handler) patterns, and run tests again. Still passes, with same coverage levels.

Were any tests performed to show use cases where the stub logic is completely replaced?

As this being a new feature there are no current test cases in OSAL or CFE that make use of the handler replacement. I did test it by specifically changing an ES test case to call UT_SetHandlerFunction() in order to confirm that this works and the default handler was not used. So I can confirm that the override concept basically works. I was hoping you'd provide some tests that will actually utilize it.

Regarding the generated stubs, they all follow a very basic pattern. Here is a random example:

/*
 * ----------------------------------------------------
 * Generated stub function for OS_BinSemCreate()
 * ----------------------------------------------------
 */
int32 OS_BinSemCreate(osal_id_t *sem_id, const char *sem_name, uint32 sem_initial_value, uint32 options)
{
    UT_GenStub_SetupReturnBuffer(OS_BinSemCreate, int32);

    UT_GenStub_AddParam(OS_BinSemCreate, osal_id_t *, sem_id);
    UT_GenStub_AddParam(OS_BinSemCreate, const char *, sem_name);
    UT_GenStub_AddParam(OS_BinSemCreate, uint32, sem_initial_value);
    UT_GenStub_AddParam(OS_BinSemCreate, uint32, options);

    UT_GenStub_Execute(OS_BinSemCreate, Basic, UT_DefaultHandler_OS_BinSemCreate);

    return UT_GenStub_GetReturnValue(OS_BinSemCreate, int32);
}

The pattern is very simple, with the goal being to:

  • capture the details about return value, make a buffer of the correct type/size to hold it
  • capture every passed in parameter, along with the name and correct type/size
  • execute the hook/handler (as every stub always has)
  • return the correct value from the buffer created earlier.

The main benefit of scripting this sort of thing is that every function stub is now exactly the same, no special sauce are added to any of them, and everything is done exactly the same way in all of them, because a script generated them all. It greatly minimizes the risk that any stub becomes "special" in any way - any the special sauce must be put into the handler, not the script-generated stub function. And thus it can be overridden, always.

@asgibson
Copy link
Contributor

A sample from my first run on a cf header:

/*
 * ----------------------------------------------------
 * Generated stub function for CF_Chunks_ComputeGaps()
 * ----------------------------------------------------
 */
uint32 CF_Chunks_ComputeGaps(const chunks_t *chunks, index_t max_gaps,
                             chunk_size_t total, chunk_offset_t start,
                             compute_gap_fn_t compute_gap_fn, void *opaque) {
  UT_GenStub_SetupReturnBuffer(CF_Chunks_ComputeGaps, uint32);

  UT_GenStub_AddParam(CF_Chunks_ComputeGaps, const chunks_t *, chunks);
  UT_GenStub_AddParam(CF_Chunks_ComputeGaps, index_t, max_gaps);
  UT_GenStub_AddParam(CF_Chunks_ComputeGaps, chunk_size_t, total);
  UT_GenStub_AddParam(CF_Chunks_ComputeGaps, chunk_offset_t, start);
  UT_GenStub_AddParam(CF_Chunks_ComputeGaps, compute_gap_fn_t, compute_gap_fn);
  UT_GenStub_AddParam(CF_Chunks_ComputeGaps, void *, opaque);

  UT_GenStub_Execute(CF_Chunks_ComputeGaps, Basic, NULL);

  return UT_GenStub_GetReturnValue(CF_Chunks_ComputeGaps, uint32);
}

Putting those stubs in their place! Ya, Basic!

@asgibson
Copy link
Contributor

asgibson commented Apr 16, 2021

Built all cf stub files that have associated headers (looks good!), will move on to trying them out on Monday.

@asgibson
Copy link
Contributor

What if I have functions that do not appear in a header, but are accessed through an extern? (can be done by hand, just curious)

extern int CF_CFDP_RecvMd(transaction_t *t);
extern int CF_CFDP_RecvFd(transaction_t *t);
extern int CF_CFDP_RecvEof(void);
extern int CF_CFDP_RecvAck(void);
extern int CF_CFDP_RecvFin(void);
extern int CF_CFDP_RecvNak(int *num_segment_requests);

@jphickey
Copy link
Contributor Author

What if I have functions that do not appear in a header, but are accessed through an extern?

This script does assume / require that functions needing stubs are prototyped in a separate header. I believe this is also required by most coding standards (i.e. that any function that serves as an interface between source units should be declared in a separate header file).

Local helper functions are an exception (i.e. functions used only within the same unit) but by definition those shouldn't need stubs, either.

My recommendation is that if you have functions which are defined and one source file and referred to in another, that you move the declaration to a common header (it can be a private header, that's fine, as long as its a header).

@jphickey
Copy link
Contributor Author

BTW - as far as my previous comment goes - I totally acknowledge that the generator script currently does exactly this (extern right in .c file) for the default handler functions. But since it isn't FSW I was betting on this being OK, since it is simpler to do. It wouldn't be that hard to make it use a separate header though.

@astrogeco
Copy link
Contributor

CCB:2021-04-21 APPROVED with changes

  • First commit is the "big one"
  • Adds a new "handler" function that is similar to the "hook" function
  • Problem with the original design of hooks: they assume a int32 output
  • Largely backwards compatible: didn't have to change any unit tests
  • Can we rename "expected return" to "expected type"? "Expected return" It is a buffer that creates a buffer to hold the value. The expectation is that the handler will produce "some value" not a specific one. There is no verification on that value itself.

@asgibson
Copy link
Contributor

I have setup and run the same experiment as I did for #839 and all tests completed as expected which verifies that the "do-no-harm" approach is intact; however, there is a caveat here. Current stubs must be transmogrified into the default hook functions if the behaviors they exhibit are to be preserved. The experiment added default hooks for two cFE API functions CFE_MSG_GetSize and CFE_MSG_GetMsgId, but the work was done by hand without any automation. It would seem that having the ability to automate the creation of default hooks from the current stubs will be a necessity.

That being said, the design appears to work well in the small simulation I have run. 14 unit tests were run without any updates on nasa/cFS@c58824d and all the tests performed as expected. cFE was updated with the two stub changes and OSAL was updated to https://github.com/jphickey/osal/tree/fix-832-stub-handler-replacements. Tests were run again, unaltered, and received the same results as before the update (no harm!). The seven experimental tests were then updated to use the new handler functionality while the control tests still used the hook functionality. The 7 experiment tests correctly changed behavior and the 7 control tests stayed static (stub control enabled!).

I will concur with the update once the conflict is resolved.

@jphickey jphickey force-pushed the fix-832-stub-handler-replacements branch from 7757078 to 6e30293 Compare April 27, 2021 02:05
@jphickey
Copy link
Contributor Author

Rebased to latest main branch, should merge clean now.

Adds the concept of a "handler" function to UT assert.  A handler
is basically the custom logic that exists between the hook function
and the return to the stub caller.  In current UT stubs, this is
hard coded, and it generally comprises setting output parameters
and translating return values as needed.

This concept adds the basic plumbing to allow the handler to be
configured just like a hook function already does.  The difference
is that the handler is directly responsible for setting all outputs.

This also includes a script to auto-generate stub functions that
match this pattern.  Given an API header file, the script extracts
the declarations, and generates a source file with stub definitions
that rely on a separate handler to deal with the needed outputs.

Note this initial commit only adds the basic framework.  It does
not change any existing stubs or tests, and is fully backward
compatible, as it is a new feature and it is a no-op unless
actually configured/used by the stub or test case.  Follow on
commits will update the stubs to use this pattern.
To imporove scriptability of generating the shared layer stubs, makes
two minor adjustments:

- adds a typedef for the callback in OS_ObjectIdIteratorProcessEntry.  This
  avoids the complex syntax of function pointer argument, which is hard to
  read but also harder to parse in a script, too.
- Splits the "os-shared-printf.h" header into two parts, adding a new
  header file "os-shared-console.h".  This is because OS_ConsoleOutput_Impl
  is implemented in a separate unit in the source, and this allows a better
  relationship between stub files and header files.
The "built-in" logic from existing stubs is converted to a
handler function and moved to a different source file.  The
generated stub references this function if it exists and
uses it as a default handler, so the default behavior is not
changed.

In this pattern the stubs in this directory are strictly
limited to the same public APIs that are defined in header
files under src/os/inc.  This has the side effect of removing
some internal stubs required for coverage test.  Those stubs
will be reinstated in the coverage specific stubs where other
internal functions are.
Update the coverage-specific (shared layer internal) stubs to use
generated stubs.
@jphickey jphickey force-pushed the fix-832-stub-handler-replacements branch from 6e30293 to e9f344b Compare April 27, 2021 02:23
@astrogeco astrogeco added the CCB:Approved Indicates code review and approval by community CCB label Apr 27, 2021
@astrogeco astrogeco changed the base branch from main to integration-candidate April 28, 2021 12:47
@astrogeco astrogeco added IC:2021-04-27 and removed CCB:Ready Pull request is ready for discussion at the Configuration Control Board (CCB) labels Apr 28, 2021
@astrogeco astrogeco merged commit c2dc403 into nasa:integration-candidate Apr 28, 2021
astrogeco added a commit to nasa/cFS that referenced this pull request Apr 28, 2021
nasa/osal#972, update documentation for read/write
nasa/osal#966, add "handler" feature to utassert stub API
nasa/osal#953, Adds local makefile and bundle/local unit test actions with coverage verification
nasa/osal#971, socket accept using incorrect record
astrogeco added a commit to nasa/cFS that referenced this pull request Apr 29, 2021
Combines:

nasa/cFE#1431
nasa/osal#975
nasa/sample_lib#61

Includes:

nasa/cFE#1379, memory pool pointer type
nasa/cFE#1289, ES child task functional test
nasa/cFE#1289, typo in macro name
nasa/cFE#1286, Remove broken BUILDDIR reference
nasa/cFE#1305, remove option for "osal_compatible"
nasa/cFE#1374, CFE_SUCCESS constant type
nasa/cFE#1316, Remove Unused Error Codes
nasa/cFE#1370, better warning about malformed startup line
nasa/cFE#1373, check status of call to `CFE_ES_CDS_CachePreload`
nasa/cFE#1384, update documentation for `CFE_ES_DeleteCDS`
nasa/cFE#1385, exception logic when app/task is not found
nasa/cFE#1372, error if alignment size not a power of two
nasa/cFE#1368, remove unneeded CFE_ES_SYSLOG_APPEND macro
nasa/cFE#1382, improve documentation for resourceID patterns
nasa/cFE#1371, assert `CFE_RESOURCEID_MAX` is a bitmask

nasa/osal#972, update documentation for read/write
nasa/osal#966, add "handler" feature to utassert stub API
nasa/osal#953, Adds local makefile and bundle/local unit test actions with coverage verification
nasa/osal#971, socket accept using incorrect record
nasa/osal#959, move async console option

nasa/sample_lib#60, replace direct ref to ArgPtr with macro
@jphickey jphickey deleted the fix-832-stub-handler-replacements branch April 29, 2021 13:14
@skliper skliper added this to the 6.0.0 milestone Sep 24, 2021
jphickey added a commit to jphickey/osal that referenced this pull request Aug 10, 2022
As the name field is a multiple of 4 bytes, there needs to be 3 bytes
of padding, not 1, to avoid implicit padding.

This doesn't change anything, it just makes the padding explicit instead
of implicit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CCB:Approved Indicates code review and approval by community CCB
Projects
None yet
4 participants