The Physical class implements private templated read() and write()
methods. There are several properties that make this approach less than
1. read() and write() are non-virtual template functions, and we would
have to link-seam mock the internals, which means hijacking
2. Even if read() and write() were virtual functions, this is made
irrelevant by the fact that we want to traverse execution paths in
setInitialState(), which is called from the Physical constructor.
Methods invoked by class constructors are bound to the implementations
specified in the constructor's class and will never dispatch to a
descendant's override. As such we have no mechanism to manipulate the
execution path without resorting to the pre-processor seam, which is
undesirable for other reasons.
3. The abstraction is poor. Physical implements the business logic of
converting interactions with the DBus interface into compatible
interactions with the sysfs LED class attributes, and shouldn't have
knowledge of how to directly interact with sysfs itself.
4. read() and write() are template functions, but the only type
parameter used in the code-base is std::string, and conversions are
left to the caller. This needlessly complicates the caller logic and
reduces readability of the callee code.
The change defines a separate class, SysfsLed, to map actions onto the
LED sysfs class attributes. SysfsLed will be provided by the
dependency-injection pattern to the Physical class by passing an
instance reference through its constructor. The lifetime of the SysfsLed
instance must exceed the lifetime of the associated Physical instance.
Further, the methods of SysfsLed are all marked as virtual and defined
to return concrete types (either unsigned long or std::string as
appropriate). This opens the door for mocking without resorting to
techniques such as using link seams, and removes templates as a point of
complication. Further, defining only a concrete class and not an
abstract base class minimises the boilerplate required as we're likely
never going to have another descendant of SysfsLed that isn't a mock
implementation (we don't need to exclude implementations by way of
Integration tests are provided for SysfsLed, which is necessary as the
class must write to the filesystem (again, unless we want to hijack
std::fstream, which seems unpalatable). Isolated temporary directories
are used to ensure the tests can be run in parallel without
interference. The tests provide 100% line coverage of SysfsLed.
Signed-off-by: Andrew Jeffery <firstname.lastname@example.org>