Skip to content

Dissolve internal unit tests; restore coverage via public interface#59

Merged
thetic merged 25 commits into
mainfrom
disunity
Apr 18, 2026
Merged

Dissolve internal unit tests; restore coverage via public interface#59
thetic merged 25 commits into
mainfrom
disunity

Conversation

@thetic
Copy link
Copy Markdown
Owner

@thetic thetic commented Apr 17, 2026

Description

The internal unit tests in tests/unit/ and tests/util/ directly imported
private implementation headers (classes like CheckedActualCall,
IgnoredActualCall, CompositeOutput, etc.) that are not part of the public
API. This PR:

  1. Dissolves those test directories — merging whatever was already in
    tests/integration/ into a flat tests/src/ tree.
  2. Restores structural coverage for every affected source file by
    adapting the deleted tests to exercise only the public include/mutiny/
    interface.

Coverage is verified with ctest --preset coverage (lcov/gcov). No source
files were changed except one dead-code removal in StringCollection.

Related Issues

Fixes # (issue number)

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Manual Verification (Optional)

  • Target: x86-64 Linux (GNU preset + coverage preset)
  • Result: 50/50 tests pass; src/test/CompositeOutput.cpp, src/mock/NamedValue.cpp, src/test/TestingFixture.cpp, include/mutiny/mock/ActualCall.hpp, and all other previously affected files reach 100% line/function coverage.

Checklist

  • I have written/updated documentation in docs/ for any user-facing changes.
  • My code follows the project's naming conventions (mu::tiny namespace, INCLUDED_MUTINY_ guards, mutiny_ C-prefix).
  • For new features, I have considered if a C-interface adapter (.h and .c.cpp) is required for parity.
  • I have reviewed the CONTRIBUTING.md file to ensure compliance with architectural guidelines.

thetic added 4 commits April 16, 2026 21:25
Remove the separate unit and util test subdirectories; move
MockFailureReporter helper into tests/integration where it is used.
Move all test sources from tests/integration/ to tests/src/ and inline
the integration CMakeLists.txt into tests/CMakeLists.txt, removing the
now-empty subdirectory.
Remove the mutiny/ subdirectory layer: move RunCMake.cmake up to
tests/ and shift configure/, discovery/, and RunCMakeTest.cmake one
level up to tests/cmake/. Update CMakeLists.txt paths accordingly.
Adapt the deleted unit tests to exercise IgnoredActualCall through the
public mock() API by capturing the ActualCall& returned by actual_call()
after ignore_other_calls():

- MockParameter: add int, const char*, void*, and String-overload of
  with_parameter_of_type to ignoreOtherCallsIgnoresWithAllKindsOfParameters
- MockReturnValue: verify return_value_or_default<T> returns the given
  default for all supported types when the call is ignored
- MockSupport: verify with_name and with_call_order return *this on an
  ignored call (framework never invokes these on IgnoredActualCall itself)

IgnoredActualCall.hpp now reaches 100% line and function coverage.
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 17, 2026

Coverage Status

coverage: 98.867% (+0.2%) from 98.629% — disunity into main

@thetic thetic changed the title Disunity Dissolve internal unit tests; restore coverage via public interface Apr 17, 2026
thetic added 17 commits April 17, 2026 09:30
Intentionally commented out in integration/CMakeLists.txt since creation
and never wired into the build. Depends on the deleted MockFailureReporterForTest.hpp.
Adapt the deleted unit tests to exercise IgnoredExpectedCall through
mock().disable() → mock().expect_one_call(), which returns the singleton.
Three tests added to MockHierarchy (which already owns the disable/enable
pattern):

- ignoredExpectedCallSetupMethodsReturnSelf: with_name, both
  with_call_order overloads, on_object
- ignoredExpectedCallParameterMethodsReturnSelf: all with_parameter
  overloads (routing through with_typed_parameter), plus
  with_unmodified_output_parameter and ignore_other_parameters
- ignoredExpectedCallAndReturnValueOverloadsReturnSelf: all
  and_return_value overloads (routing through and_return_typed_value)

IgnoredExpectedCall.hpp now reaches 100% line and function coverage.
The signed char overload was already tested; add the parallel unsigned
char case to bring String.hpp line coverage to 100%.
Adapt the deleted unit tests to exercise JUnitOutput through
JUnitOutputPlugin::create_output() rather than the private header.
Mock file I/O via the public Output::fopen_/fputs_/fclose_ statics.
Package name is configured via parse_arguments("-pjunit=<name>");
passing argv[0]="" with plain "-pjunit" yields an empty package and
the default "mutiny.xml" filename, matching the deleted tests.

FileForJUnitTestOutputs::line() is reimplemented with pointer
arithmetic over the buffer, removing the dependency on the private
StringCollection header.

41 tests added; JUnitOutput.cpp reaches 96.4% line coverage.
Add tests in TEST_GROUP(Support) that exercise with_call_order,
with_parameter_of_type, on_object, has_return_value, and return_value
through the mock().tracing(true) + mock().actual_call() path. Remove
ActualCallTrace::clear(), which was dead code unreachable through the
public ActualCall interface.
Consolidate to a single constructor with a default parameter so the
default-constructor body is no longer dead code. Remove
get_input_parameter and get_input_parameter_type, which have no callers
anywhere in the codebase.

Add tests in TEST_GROUP(Support) that exercise:
- on_object matching path (was_passed_to_object, relates_to_object)
- range call order branch in call_to_string (with_call_order(1, 3))
- multiple-input-parameter comma in call_to_string
- input-plus-output-parameter comma in call_to_string
- multiple-output-parameter comma in call_to_string
- get_actual_calls_fulfilled via the "unexpected additional call" path
- multiple-input comma in missing_parameters_to_string
- input-plus-output comma in missing_parameters_to_string

Result: 100% function coverage (45/45), 99% line coverage (205/207,
two uncovered lines are closing-brace LCOV artifacts).
Remove dead-code methods size() and amount_of_unfulfilled_expectations()
(no callers anywhere in the codebase), and add two tests to cover the
only_keep_expectations_on_object null-assignment branch and the
only_keep_unmatching_expectations discard-finalized-match branch.
Add test exercising return_value() on an ignored call — the only
uncovered path. Required interface implementation (pure virtual in
ActualCall base); cannot be removed.
Add IgnoredShell.test.cpp to cover the get_macro_name() "TEST" branch
(run_ignored_ = true), the only uncovered line. Also registers the new
test file in tests/CMakeLists.txt.
Add two tests: one exercises the basename_from_path slash-assignment
branch (argv[0] containing '/'), and one exercises the parse_arguments
early return when -pjunit is followed by an invalid non-'=' character.
Remove dead with_name() and with_call_order() overrides: the framework
always uses set_name_and_check() directly. Make the ActualCall base
methods non-pure (default no-op) so CheckedActualCall no longer needs
to implement them.

Add four tests covering: has_failed() guard early returns in
check_input_parameter() and on_object(), the String-overload
no-comparator path in with_parameter_of_type(), return_value() when
no matching expectation exists, and three chained output parameters
(exercises the add_output_parameter while-loop body).
The out-of-bounds path was dead code — callers always check size()
before indexing. Removing it and the empty_ member improves coverage.
Adapt the deleted unit tests to exercise NamedValue directly through its
public API (NamedValue.hpp is a public header):

- Primitive set/get/type: all 11 non-double types (bool through function
  pointer), plus memory buffer, const/mutable object pointers
- Utility methods: get_name, set_name, set_size, is_const_object,
  get_const_object_pointer, get_object_pointer, get_comparator, get_copier
- Move constructor
- equals(): same-type for all primitives; 15 cross-type integer reverse
  directions not reached by MockParameter (which always does
  expected.equals(actual)); memory buffer size mismatch; type mismatch;
  object type without and with comparator
- to_string(): every branch including the "No comparator found" fallback
  and comparator-delegating path
- compatible_for_copying(): same type, const void*/void*, incompatible types
- New NamedValueRepository group: set/get_default_comparators_and_copiers
  _repository; comparator and copier lookup via set_const_object_pointer
  and set_object_pointer

NamedValue.cpp now reaches 100% line and function coverage (374/374,
47/47).
Four lines in TestingFixture.cpp were unreachable from existing tests:

- get_ignore_count(): add an IgnoredShell to the fixture and verify the
  count is 1 after run_all_tests().

- check_test_fails_with_proper_test_location(), failure-count guard
  (line 173): use a double-nested fixture whose inner test body creates a
  fresh fixture with 0 failures and calls check_test_fails_with_proper
  _test_location; the FAIL_TEST_LOCATION fires inside the inner context,
  confirmed via inner.has_test_failed().

- check_test_fails_with_proper_test_location(), line-executed guard
  (line 186): same double-nesting, but with exactly 1 failure in the
  inner fixture followed by an explicit line_executed_after_check() call
  to set the static flag before check_test_fails_with_proper_test_location
  is invoked.

TestingFixture.cpp now reaches 100% line and function coverage
(97/97, 26/26).
Lines 44 and 47 of ActualCall.hpp are the default no-op bodies of
with_name() and with_call_order() in the ActualCall base class.  They are
unreachable in practice because:

- IgnoredActualCall overrides both.
- ActualCallTrace overrides both.
- CheckedActualCall inherits the defaults but mock() calls set_name_and_check()
  on it directly, bypassing with_name() and with_call_order() entirely.

The only path to these lines is to call them explicitly on the ActualCall&
reference returned by mock().actual_call() for a matched expectation, which
resolves to the CheckedActualCall instance.  One test added to MockSupport:

  checkedActualCallWithNameAndCallOrderUseBaseClassNoOps

ActualCall.hpp now reaches 100% line and function coverage across all
translation units.
Exercise the 10 previously uncovered methods (color, print_buffer,
print(const char*), print(long), print(size_t), print_double,
print_failure, set_progress_indicator, print_very_verbose, flush)
through a CommandLineRunner subclass that intercepts
create_composite_output() to capture the composite Output*.

A custom Plugin/Output pair that returns needs_console_companion()=true
triggers composite creation without requiring JUnit or file I/O.
thetic added 3 commits April 17, 2026 09:39
The only call site was a "This cannot happen" FAIL_TEST assertion in
CheckedActualCall::check_expectations().  The condition it guards
(state == in_progress AND a finalized matching expectation still in the
list) is structurally impossible: complete_call_when_match_is_found()
always removes a finalized match and sets state = success before
check_expectations() reaches that branch.  The original coverage came
from unit tests that exercised ExpectedCallsList in isolation using
private-class internals unavailable through the public interface.

Remove has_finalized_matching_expectations() from ExpectedCallsList and
the dead guard block from CheckedActualCall::check_expectations().
The has_failed() guard in check_output_parameter() was unreachable
because no test passed an output parameter after a failing actual call.
Verify the postcondition: the output buffer is not written when the
call has already failed.
Exercises the skip_test function body through the SKIP_TEST macro;
asserts no failure is recorded and execution halts at the skip point.
@thetic thetic marked this pull request as ready for review April 17, 2026 16:56
Tests MockReturnValue.returnValueWithNoActualCallReturnsEmptyNamedValue
exercises the last_actual_function_call_ == nullptr guard in
Support::return_value() (mock.cpp:563), symmetrically covering the
sibling null-path already tested in has_return_value().
@thetic thetic merged commit 536857d into main Apr 18, 2026
40 checks passed
@thetic thetic deleted the disunity branch April 18, 2026 01:11
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.

2 participants