202 lines
10 KiB
Text
202 lines
10 KiB
Text
how to crack nut #47
|
|
====================
|
|
:toc:
|
|
|
|
.collection of successfully employed technical solutions
|
|
Some nasty problems are recurring time and again. Maybe a trick could be found somewhere
|
|
in the net, and a library function was created to settle this damn topic once and for all.
|
|
Maybe even a nice test and demo is provided. And then the whole story will be forgotten.
|
|
|
|
_Sounds familiar?_ => ☹☻☺👻 ~[red]#... then please consider to leave some traces here ...#~
|
|
|
|
|
|
Methods
|
|
-------
|
|
Mathematics
|
|
~~~~~~~~~~~
|
|
- some basic descriptive statistics computations are defined in 'lib/stat/statistic.hpp'
|
|
- the simple case for _linear regression_ is also implemented there
|
|
- Gnuplot provides also common statistics functions, which may come in handy when the
|
|
goal is anyway to create a visualisation (-> see <<_investigation,below>>)
|
|
|
|
|
|
|
|
|
|
Situations
|
|
----------
|
|
Investigation
|
|
~~~~~~~~~~~~~
|
|
summary test::
|
|
Reformulate the research and the findings into a test, which can be read top-down like a novel.
|
|
Start with documenting the basics, package helpers into a tool class, or package setup into a
|
|
workbench-style class, with individual tool modules. Provide a short version of this test with
|
|
the basic demo, which should be able to run with the regular test suite. Extended long-running
|
|
tests can be started conditionally with commandline-arguments. See 'scheduler-stress-test.cpp'
|
|
visualisation::
|
|
Use code generation to visualise data structures or functions and observation statistics.
|
|
Typically these generation statements can be packaged into an invocation helper and included
|
|
into a relevant test, but commented-out there.
|
|
+
|
|
- generate Graphviz diagrams: 'lib/dot-gen.hpp' provides a simple DSL. See 'test-chain-load-test.cpp'
|
|
- generate Gnuplot scripts: use the xref:texttemplate[Text-Template] engine to fill in data, possibly
|
|
from a generated data table
|
|
|
|
* 'lib/gnuplot-gen.hpp' provides some pre-canned scripts for statistics plots
|
|
* used by the 'stress-test-rig.cpp', in combination with 'data.hpp' to collect measurement results
|
|
|
|
Testing
|
|
~~~~~~~
|
|
verify structured data::
|
|
build a diagnostic output which shows the nesting
|
|
- use configuration by template parameters and use simple numbers or strings as part components
|
|
- render the output and compare it with `""_expect` (the `ExpectStr` -> see 'lib/test/test-helper.hpp')
|
|
- break the expect-string into parts with indentation, by exploiting the C _string gaps_
|
|
|
|
However, this works only up to a certain degree of complexity.
|
|
|
|
A completely different approach is to transform the structured result-data into an ETD (`GenNode` tree)
|
|
and then directly compare it to an ETD that is created in the test fixture with the DSL notation (`MakeRec()`)
|
|
|
|
verify floating point data::
|
|
+
|
|
- either use approximate comparison
|
|
|
|
* `almostEqual()` -> see 'lib/test/test-helper.hpp'
|
|
* 'util-quant.hpp' has also an `almostEqual(a,b, ulp)`
|
|
* [yellow-background]#TODO 2024# should be sorted out -> https://issues.lumiera.org/ticket/1360[#1360]
|
|
|
|
- or render the floating point with the diagnostic-output functions, which deliberately employ
|
|
a built-in rounding to some sensible amount of places (which in most cases helps to weed-out the
|
|
``number dust'')
|
|
- simply match it against an `ExpectStr` -- which implicitly converts to string, thereby
|
|
also applying the aforementioned implicit rounding
|
|
+
|
|
-> See also <<_formatting,about formatting>> below; in a nutshell, `#include 'lib/format-output.hpp'`
|
|
|
|
verify fluctuating values::
|
|
A first attempt should be made to bring them into some regular band. This can be achieved by
|
|
automatically calibrating the measurement function (e.g. do a timing calibration beforehand).
|
|
Then the actual value can be matched by the `isLimited(l, val, u)` notation (see 'lib/uitl.hpp')
|
|
test with random numbers::
|
|
+
|
|
- Control the seed! Invoke the seedRand() function once in each test instance.
|
|
This draws an actually random number as seed and re-seeds the `lib::defaultGen`. The seed is written to the log.
|
|
- But the seed can be set to a fixed value with the `--seed` parameter of the test runner.
|
|
This is how you reproduce a broken test case.
|
|
- Moreover, be sure either to draw all random values from the `defaultGen` or to build a well organised
|
|
tree of PRNG instances, which seed from each other. This is especially valuable when the test starts
|
|
several threads; each thread should use its own generator then (yet this can be handled sloppy if
|
|
the _quality_ of the random number actually does not matter!)
|
|
|
|
|
|
|
|
Common Tasks
|
|
------------
|
|
Data handling
|
|
~~~~~~~~~~~~~
|
|
persistent data set::
|
|
use the `lib::stat::DataTable` ('data.hpp') with CSV rendering -> see 'data-csv-test.cpp'
|
|
structured data::
|
|
represent it as Lumiera ETD, that is as a tree of `GenNode` elements.
|
|
+
|
|
- be sure to understand the _essential idea:_ the receiver should act based on _knowledge_
|
|
about the structure -- not by _introspection and case-switching_
|
|
- however -- for the exceptional case that you _absolutely must discover_ the structure,
|
|
then use the visitor feature. This has the benefit of concentrating the logic at one place.
|
|
- can represent the notion of a nested scope, with iteration
|
|
- provides a convenient DSL notation, especially for testing, which helps to explain
|
|
the expected structure visually .
|
|
- also useful as output format, both for debugging and because it can be matched against
|
|
an expected structure, which is generated with the DSL notation.
|
|
- [yellow-background]#(planned)# will be able to map this from/to JSON easily.
|
|
|
|
Iterating
|
|
~~~~~~~~~
|
|
Lumiera iterators::
|
|
They are designed for convenient usage with low boilerplate (even while this means wasting
|
|
some CPU cycles or memory). They are deliberately much simpler than STL iterators, can be
|
|
iterated only once, can be bool checked for iteration end, and can be used both in a for-each
|
|
construct and in while-loops.
|
|
IterExplorer::
|
|
The function `lib::explore(IT)` builds on top of these features and is meant to basically
|
|
iterate anything that is iterable -- this can be used to level and abstract away the details.
|
|
+
|
|
- can be filtered and transformed
|
|
- can be reduced or collected into a vector
|
|
- can be used to build complex layered search- and evaluation schemes
|
|
STL-adaptors::
|
|
A set of convenience functions like `eachElm(CON)` -- available in 'iter-adapter-stl.hpp'.
|
|
Also allows to iterate _each key_ or _value_, and to take snapshots
|
|
IterSource::
|
|
This is a special Lumiera iterator implementation which delegates through a virtual (OO) interface.
|
|
Thus the source of the data can be abstracted away and switched transparently at runtime.
|
|
Especially relevant for APIs, to produce a data sequence once and without coupling to details;
|
|
even the production-state can be hooked up into the IterSource instance (with a smart-ptr).
|
|
This allows e.g. to send a Diff to the UI through the UI-Bus, while the actual generation
|
|
and application both happen demand-driven or _lazy..._
|
|
|
|
|
|
Formatting
|
|
~~~~~~~~~~
|
|
- implement a conversion-to-string operator.
|
|
- include the C++ IOStreams via 'lib/format-cout.hpp' -> this magically uses the `util::toString()`
|
|
- for testing, temporarily include 'lib/test/diagnostic-output' and use the `SHOW_EXPR` macro.
|
|
- use 'util::join' ('lib/format-util.hpp') to join arbitrary elements with `util::toString()` conversion
|
|
- use _printf-style formatters_ from Boost-format. We provide a light-weight front-end via 'lib/format-string.hpp'
|
|
|
|
* the heavyweight boost-format include is only required once, for 'lib/format-string.cpp'.
|
|
* the templated front-end passes-through most basic types and types with string-conversion
|
|
* all invocations are strictly error safe (never throw) and can thus be used from catch-handlers
|
|
|
|
- use the anchor:texttemplate[lib/text-template]*Text-Template* engine. See 'text-template-test.cpp'.
|
|
Can be used with simple map bindings, or even a _definition string_ `"x=42, y=why-not?"`, but can also
|
|
draw data o from a `lib::GenNode` tree, or even from a custom defined `DataSource` template.
|
|
Supports placeholders, conditionals and simple loops (and that's it --
|
|
because there are way more capable solutions _out there_ ☺)
|
|
|
|
|
|
Language constructs
|
|
-------------------
|
|
Templates
|
|
~~~~~~~~~
|
|
build-from-anything::
|
|
use a templated constructor, possibly even with varargs
|
|
+
|
|
- use a _deduction guide_ to pick the right ctor and arguments -> see
|
|
|
|
* `ThreadJoinable` in 'thread.hpp', 698
|
|
* `DataSource<string>` specialisation only when argument can be converted to string,
|
|
in 'text-template.hpp', 745
|
|
|
|
- prevent shadowing of _automatically generated copy operations._
|
|
See https://issues.lumiera.org/ticket/963[#963]. Based on the ``disable if'' SFINAE technique.
|
|
A ready-made templated typedef `lib::meta::disable_if_self` can be found in 'lib/meta/util.hpp'
|
|
|
|
|
|
Variadics
|
|
~~~~~~~~~
|
|
pick and manipulate individually::
|
|
The key trick is to define an _index sequence template,_ which can then be matched against
|
|
a processing template for a single argument; and the latter can have partial specialisations
|
|
+
|
|
- see 'variadic-argument-picker-test.cpp'
|
|
- but sometimes it is easier to use the tried and true technique of the Loki-Typelists, which
|
|
can be programmed recursively, similar to LISP. The »bridge« is to unpack the variadic argument pack
|
|
into the `lib::meta::Types<ARGS...>`
|
|
+
|
|
apply functor to each tuple element::
|
|
A common trick is to use `std::apply` in combination with a _fold-expression_
|
|
+
|
|
- provided as `lib::meta::forEach` in 'lib/meta/tuple-helper.hpp
|
|
- The design of the `DataTable` with CSV-Formatting is based on this technique, see 'lib/stat/data.hpp'
|
|
- 'lib/iter-zip.hpp' uses this to construct a tuple-of-iterators
|
|
+
|
|
unpack iterator into tuple::
|
|
Under controlled conditions this is possible (even while it seems like time travel from the runtime into
|
|
the compile-time domain). The number of results to extract from the iterator must be known at compile time,
|
|
and the possible result types must be limited, so that a visitor can be used for double-dispatch.
|
|
+
|
|
- see tuple-record-init-test.cpp
|
|
- used in 'command-simple-closure.hpp' to receive parameter values sent via UI-Bus and package them
|
|
into a tuple for invocation of a Steam-Layer command.
|
|
|