Browse Source

Clean up and rewrite most docstrings

A big spring clean of the docstrings (sure long time coming!)

 *  Fix typos,

 *  rewrite clumsy explanations,

 *  omit unnecessary and misleading parts,

 *  remove awkward and "smart" language,

 *  use consistent here-doc formatting across small and big docstrings
    (isolate quote mark triplets on own line.)
Alois Mahdal 1 year ago
parent
commit
138b43b17e
2 changed files with 281 additions and 142 deletions
  1. 16
    7
      sznqalibs/bottleneck.py
  2. 265
    135
      sznqalibs/hoover.py

+ 16
- 7
sznqalibs/bottleneck.py View File

@@ -2,7 +2,9 @@ import time
2 2
 
3 3
 
4 4
 class FrameState:
5
-    """Abstraction and tracking of frame state"""
5
+    """
6
+    Abstraction and tracking of frame state
7
+    """
6 8
 
7 9
     def __init__(self, max_load, size, debug):
8 10
         self.start = 0
@@ -53,7 +55,8 @@ class FrameState:
53 55
 
54 56
 
55 57
 class Throttle:
56
-    """Throttle to allow only certain amount of iteration per given time.
58
+    """
59
+    Throttle to allow only certain amount of iteration per given time.
57 60
 
58 61
     Usage:
59 62
 
@@ -75,11 +78,11 @@ class Throttle:
75 78
     of calls in time.  If your loop takes 1ms and you throttle to 1000 loops
76 79
     per 10 minutes, all loops will happen in the first second, and the last
77 80
     call will block for 599 seconds.
78
-
79 81
     """
80 82
 
81 83
     def __init__(self, max_load, frame_size=60, debug=False):
82
-        """Create new Throttle.
84
+        """
85
+        Create new Throttle.
83 86
 
84 87
         Only required parameter is `max_load`, which is number of times per
85 88
         frame `Throttle.wait()` returns without blocking.  Optionally you can
@@ -95,15 +98,21 @@ class Throttle:
95 98
                                 debug=self.debug)
96 99
 
97 100
     def is_closed(self):
98
-        """True if throttle is closed."""
101
+        """
102
+        True if throttle is closed.
103
+        """
99 104
         return self.frame.is_closed()
100 105
 
101 106
     def is_open(self):
102
-        """True if throttle is open."""
107
+        """
108
+        True if throttle is open.
109
+        """
103 110
         return self.frame.is_open()
104 111
 
105 112
     def wait(self):
106
-        """Return now if throttle is open, otherwise block until it is."""
113
+        """
114
+        Return now if throttle is open, otherwise block until it is.
115
+        """
107 116
         self.frame.debug()
108 117
         self.waiting = self.is_closed()
109 118
         while self.waiting:

+ 265
- 135
sznqalibs/hoover.py View File

@@ -141,7 +141,9 @@ def regression_test(argsrc, tests, driver_settings=None, cleanup_hack=None,
141 141
 
142 142
 
143 143
 def get_data_and_stats(driverClass, argset, driver_settings, only_own=False):
144
-    """Run test with given driver"""
144
+    """
145
+    Run single test, return data and stats.
146
+    """
145 147
     start = time.time()
146 148
     d = driverClass()
147 149
     d.setup(driver_settings, only_own=only_own)
@@ -150,7 +152,9 @@ def get_data_and_stats(driverClass, argset, driver_settings, only_own=False):
150 152
 
151 153
 
152 154
 def get_data(driverClass, argset, driver_settings, only_own=False):
153
-    """Run test with given driver"""
155
+    """
156
+    Run single test, return data only.
157
+    """
154 158
     d = driverClass()
155 159
     d.setup(driver_settings, only_own=only_own)
156 160
     d.run(argset)
@@ -194,15 +198,25 @@ class RuleOp:
194 198
 
195 199
     @staticmethod
196 200
     def Match(pattern, item_ok):
197
-        """Evaluate set of logically structured patterns using passed function.
201
+        """
202
+        Evaluate set of logically structured patterns using passed function.
203
+
204
+        *pattern* must be a tuple in form of `(op, items)` where *op* can be
205
+        either `RuleOp.ALL` or `RuleOp.ANY` and *items* is a list of items
206
+        to check using *item_ok* function.
198 207
 
199
-        pattern has form of `(op, [item1, item2, ...])` where op can be any of
200
-        pre-defined logical operators (`ALL`/`ANY`, I doubt you will ever need
201
-        more) and item_ok is a function that will be used to evaluate each one
202
-        in the list.  In case an itemN is actually pattern as well, it will be
203
-        recursed into, passing the item_ok on and on.
208
+        *item_ok* is a function that accepts single argument and its return
209
+        value is evaluated for true-ness.
204 210
 
205
-        Note that there is no data to evaluate "against",  you can use closure
211
+        Final result is True or False and is computed by combining results
212
+        of individual *item_ok* calls: either all must be true (when `op
213
+        == RuleOp.ALL`) or at least one must be true (when `op == RuleOp.ANY`).
214
+
215
+        The evaluation is done recursively, that is, if an item in the pattern
216
+        is also a pattern itself, it will be evaluated by calling RuleOp.Match
217
+        and passing the same *item_ok* function.
218
+
219
+        Note that there is no data to evaluate "against", you can use closure
206 220
         if you need to do that.
207 221
         """
208 222
 
@@ -226,8 +240,8 @@ class RuleOp:
226 240
 class DictPath:
227 241
     """Mixin that adds "path-like" behavior to the top dict of dicts.
228 242
 
229
-    Use this class as a mixin for a deep dic-like structure and you can access
230
-    the elements using a path.  For example:
243
+    Use this class as a mixin for a deep dictionary-like structure in order to
244
+    access the elements using a Unix-like path.  For example:
231 245
 
232 246
         MyData(dict, DictPath):
233 247
             pass
@@ -335,12 +349,13 @@ class DictPath:
335 349
 # ########################################################################### #
336 350
 
337 351
 class TinyCase(dict, DictPath):
338
-    """Abstraction of the smallest unit of testing.
352
+    """Test case for hoover.
339 353
 
340
-    This class is intended to hold relevant data after the actual test
341
-    and apply transformations (hacks) as defined by rules.
354
+    This class is used as an intermediary container for test parameters,
355
+    oracles and test results.  This is to allow post-test transformations
356
+    ("hacks") to happen before the result is evaluated for pass/fail.
342 357
 
343
-    The data form (self) is:
358
+    Instantiate TinyCase with data (self) in following format:
344 359
 
345 360
         {
346 361
             'argset': {},   # argset as fed into `BaseTestDriver.run`
@@ -350,40 +365,19 @@ class TinyCase(dict, DictPath):
350 365
             'rname': ""     # name of result driver's class
351 366
         }
352 367
 
353
-    The transformation is done using the `TinyCase.hack()` method to which
354
-    a list of rules is passed.  Each rule is applied, and rules are expected
355
-    to be in a following form:
368
+    Then call TinyCase.hack() with a set of rules which can alter oracles,
369
+    results or both based on the data stored in TinyCase.
356 370
 
357
-        {
358
-            'drivers': [{}],        # list of structures to match against self
359
-            'argsets': [{}],        # -ditto-
360
-            'action_name': <Arg>    # an action name with argument
361
-        }
371
+    Typical use cases for 'hacks' are:
372
+
373
+     *  avoid known and tracked bugs,
374
+     *  help normalize results (remove irrelevant details),
375
+     *  solve certain limitations in oracle machines.
362 376
 
363
-    For each of patterns ('drivers', argsets') present, match against self
364
-    is done using function `hoover.dataMatch`, which is basically a recursive
365
-    test if the pattern is a subset of the case.  If none of results is
366
-    negative (i.e. both patterns missing results in match), any known actions
367
-    included in the rule are called.  Along with action name a list or a dict
368
-    providing necessary parameters is expected: this is simply passed as only
369
-    parameter to corresponding method.
370
-
371
-    Actions use specific way how to address elements in the structures
372
-    saved in the oracle and result keys provided by `DictPath`, which makes
373
-    it easy to define rules for arbitrarily complex dictionary structures.
374
-    The format resembles to Unix path, where "directories" are dict
375
-    keys and "root" is the `self` of the `TinyCase` instance:
376
-
377
-        /oracle/temperature
378
-        /result/stats/word_count
379
-
380
-    Refer to each action's docstring for descriprion of their function
381
-    as well as expected format of argument.  The name of action as used
382
-    in the reule is the name of method without leading 'a_'.
383
-
384
-    Warning: All actions will silently ignore any paths that are invalid
385
-             or leading to non-existent data!
386
-             (This does not apply to a path leading to `None`.)
377
+    Note that while for most tests, you should strive for zero hacks,
378
+    sometimes they are inevitable.  In such cases, number of hacks can
379
+    be a useful quality metric.   For that reason, 'hoover.regression_test'
380
+    will count the applied hacks and return it in the test report.
387 381
     """
388 382
 
389 383
     def a_exchange(self, action):
@@ -479,7 +473,57 @@ class TinyCase(dict, DictPath):
479 473
                      'round': a_round}
480 474
 
481 475
     def hack(self, ruleset):
482
-        """Apply action from each rule, if patterns match."""
476
+        """
477
+        Run any matching actions in the *ruleset*.
478
+
479
+        Each rule must be in in a following form:
480
+
481
+            {
482
+                'drivers': [{}],        # list of structures to match
483
+                                        # against self
484
+                'argsets': [{}],        # -ditto-
485
+                <action_name>: <Arg>    # an action name with argument
486
+                <action_name>: <Arg>    # another action...
487
+            }
488
+
489
+        Each of the rules is first evaluated for match (does it apply to this
490
+        TinyCase?), and if the rule applies, transformation is done.  The
491
+        transformation is defined by `<action_name>: <Arg>` pairs and it can
492
+        alter 'oracle', 'result' or both.
493
+
494
+        The match evaluation is done using `hoover.dataMatch()` -- this is
495
+        basically a recursive pattern match against 'drivers' and 'argsets'.
496
+        Both 'drivers' and 'argsets' are optional, but when specified, all
497
+        items must must match in order for the rule to apply.  (If 'drivers'
498
+        and 'argsets' are both missing or empty, rule will apply to each and
499
+        all test cases.)
500
+
501
+        If rule does not match, `TinyCase.hack()` moves on to next one.
502
+
503
+        If a rule does match, `TinyCase.hack()` will look for actions defined
504
+        in it.  Action consists of action name (key of the rule dictionary,
505
+        <action_name>) and an argument (<Arg>).
506
+
507
+        Action name must be one of: 'remove', 'even_up', 'format_str',
508
+        'exchange' or 'round'.  Each action corresponds to a TinyCase method
509
+        prefixed by 'a_'; for example 'even_up' action corresponds to
510
+        TinyCase.a_even_up method.  Each action expects different argument
511
+        so see the corresponding method docstrings.
512
+
513
+        Because 'oracle' and 'result' can be relatively complex structures,
514
+        actions accept Unix-like paths to specify elements inside them.
515
+        The "root" of the path is the TinyCase instance, and "directories"
516
+        are keys under it.  For example, following would be valid paths
517
+        if test drivers work with dictionaries such as `{'temperature': 50,
518
+        'stats': {'word_count': 15}}`:
519
+
520
+            /oracle/temperature
521
+            /result/stats/word_count
522
+
523
+        Warning: All actions will silently ignore any paths that are invalid
524
+                 or leading to non-existent data!
525
+                 (This does not apply to a path leading to `None`.)
526
+        """
483 527
 
484 528
         def driver_matches(rule):
485 529
             if 'drivers' not in rule:
@@ -557,33 +601,62 @@ class DriverDataError(Exception):
557 601
 class BaseTestDriver:
558 602
     """Base class for test drivers used by `hoover.regression_test` and others.
559 603
 
560
-    This class is used to create a test driver, which is an abstraction
561
-    and encapsulation of the system being tested.  Or, the driver in fact
562
-    can be just a "mock" driver that provides data for comparison with
563
-    a "real" driver.
564
-
565
-    The minimum you need to create a working driver is to implement a working
566
-    `self._get_data` method that sets `self.data`.  Any exception from this
567
-    method will be re-raised as DriverError with additional information.
568
-
569
-    Also, you can set self.duration (in fractional seconds, as returned by
570
-    standard time module) in the _get_data method, but if you don't, it is
571
-    measured for you as time the method call took.  This is useful if you
572
-    need to fetch the data from some other driver or a gateway, and you
573
-    have better mechanism to determine how long the action would take "in
574
-    real life".
575
-
576
-    For example, if we are testing a Java library using a Py4J gateway,
577
-    we need to do some more conversions outside our testing code just to
578
-    be able to use the data in our Python test.  We don't want to include
579
-    this in the "duration", since we are measuring the Java library, not the
580
-    Py4J GW (or our ability to perform the conversions optimally).  So we
581
-    do our measurement within the Java machine and pass the result to the
582
-    Python driver.
604
+    This class tepresents test driver and can be used to:
605
+
606
+     *  Wrap system under test (SUT).
607
+
608
+        Provide simple interface to set up, sandbox and activate the system
609
+        and collect any relevant results.  This can be merely return value
610
+        (purely functional test) but also other characteristics such as
611
+        time to complete.
612
+
613
+     *  Mimic ("mock") the system under test.
614
+
615
+        Also called as oracle machine, this can be used to predict expected
616
+        behavior of SUT under given parameters.
617
+
618
+     *  Wrap an alternative implementation of SUT.
619
+
620
+        As a special case of the previous role, sometimes it's desirable to
621
+        use an alternative implementation of SUT as oracle machine.  This
622
+        can be a legacy implementation, reference implementation or other
623
+        platform implementation.
624
+
625
+    In either case, the driver makes sure that any input arguments are
626
+    interpreted (and passed on) correctly and any results are returned in
627
+    a consistent way.
628
+
629
+    To use this class, sub-class it and implement `_get_data()` method.
630
+    Tge `_get_data()` method must:
631
+
632
+     *  Accept single argument; this contains arguments to the SUT.
633
+
634
+        If using `hoover.regression_test()`, this value will be retrieved
635
+        from the *argsrc* iterator.
636
+
637
+     *  Implement the test case defined by the argument set.
638
+
639
+        The implementation can either be a wrapper to real SUT, alternative
640
+        one, or can be an oracle machine -- i.e. it can figure out the result
641
+        on its own.  Note that this can be much easier as it sounds, given
642
+        that you can "cheat" by crafting the set of test cases so that the
643
+        prediction is easy (but still effective at hitting bugs), or you
644
+        can "hide the answer" in the *args* itself, and define set of
645
+        test cases statically in form of "question, answer" pairs.
646
+
647
+     *  Collect any relevant data and set it to `data` property.
648
+
649
+        Optionally, you can also set `duration` property (in fractional
650
+        seconds, as returned by standard time module).  If you don't
651
+        it will be automatically measured.
652
+
653
+    Any exception from the *_get_data* method will be re-raised as
654
+    DriverError.
583 655
 
584 656
     Optionally, you can:
585 657
 
586
-    *   Make an __init__ and after calling base __init__, set
658
+    *   Implement *__init__* method calling base __init__ and setting more
659
+        properties:
587 660
 
588 661
         *   `self._mandatory_args`, a list of keys that need to be present
589 662
             in `args` argument to `run()`
@@ -591,7 +664,7 @@ class BaseTestDriver:
591 664
         *   and `self._mandatory_settings`, a list of keys that need to be
592 665
             present in the `settings` argument to `__init__`
593 666
 
594
-    *   implement methods
667
+    *   Implement methods
595 668
 
596 669
         *   `_decode_data` and `_normalize_data`, which are intended to decode
597 670
              the data from any raw format it is received, and to prepare it
@@ -683,15 +756,25 @@ class BaseTestDriver:
683 756
 
684 757
     @classmethod
685 758
     def check_values(cls, args=None):
686
-        """check args in advance before running or setting up anything"""
759
+        """
760
+        Check args in advance before running or setting up anything.
761
+        """
687 762
         for fn in cls.bailouts:
688 763
             if fn(args):
689 764
                 raise NotImplementedError(inspect.getsource(fn))
690 765
 
691 766
     def setup(self, settings, only_own=False):
692
-        """Load settings. only_own means that only settings that belong to us
693
-        are loaded ("DriverClass.settingName", the first discriminating part
694
-        is removed)"""
767
+        """
768
+        Load settings.
769
+
770
+        If *only_own* is false, *settings* are merely assigned to
771
+        settings attribute.
772
+
773
+        if *only_own* is true, settings are filtered:  Any keys that don't
774
+        begin with the prefix of driver class name and period are ignored.
775
+        Settings that do start with this prefix are assigned to settings
776
+        attribute with the prefix removed.
777
+        """
695 778
         if only_own:
696 779
             for ckey in settings:
697 780
                 driver_class_name, setting_name = ckey.split(".", 2)
@@ -702,7 +785,9 @@ class BaseTestDriver:
702 785
         self._setup_ok = True
703 786
 
704 787
     def run(self, args):
705
-        """validate, run and store data"""
788
+        """
789
+        Validate args, run SUT and store data.
790
+        """
706 791
 
707 792
         self._args = args
708 793
         assert self._setup_ok, "run() before setup()?"
@@ -736,7 +821,9 @@ class MockDriverTrue(BaseTestDriver):
736 821
 # ########################################################################### #
737 822
 
738 823
 class StatCounter:
739
-    """A simple counter with formulas support."""
824
+    """
825
+    A simple counter with support for custom formulas.
826
+    """
740 827
 
741 828
     def __init__(self):
742 829
         self.generic_stats = {}
@@ -807,18 +894,24 @@ class StatCounter:
807 894
         return computed
808 895
 
809 896
     def add_formula(self, vname, formula):
810
-        """Add a function to work with generic_stats, driver_stats."""
897
+        """
898
+        Add a function to work with generic_stats, driver_stats.
899
+        """
811 900
         self.formulas[vname] = formula
812 901
 
813 902
     def add(self, vname, value):
814
-        """Add a value to generic stat counter."""
903
+        """
904
+        Add a value to generic stat counter.
905
+        """
815 906
         if vname in self.generic_stats:
816 907
             self.generic_stats[vname] += value
817 908
         else:
818 909
             self.generic_stats[vname] = value
819 910
 
820 911
     def add_for(self, dclass, vname, value):
821
-        """Add a value to driver stat counter."""
912
+        """
913
+        Add a value to driver stat counter.
914
+        """
822 915
         dname = dclass.__name__
823 916
         if dname not in self.driver_stats:
824 917
             self._register(dname)
@@ -828,15 +921,21 @@ class StatCounter:
828 921
             self.driver_stats[dname][vname] = value
829 922
 
830 923
     def count(self, vname):
831
-        """Alias to add(vname, 1)"""
924
+        """
925
+        Alias to add(vname, 1)
926
+        """
832 927
         self.add(vname, 1)
833 928
 
834 929
     def count_for(self, dclass, vname):
835
-        """Alias to add_for(vname, 1)"""
930
+        """
931
+        Alias to add_for(vname, 1)
932
+        """
836 933
         self.add_for(dclass, vname, 1)
837 934
 
838 935
     def all_stats(self):
839
-        """Compute stats from formulas and add them to colledted data."""
936
+        """
937
+        Compute stats from formulas and add them to colledted data.
938
+        """
840 939
         stats = self.generic_stats
841 940
         for dname, dstats in self.driver_stats.items():
842 941
             for key, value in dstats.items():
@@ -846,7 +945,8 @@ class StatCounter:
846 945
 
847 946
 
848 947
 class Tracker(dict):
849
-    """Error tracker to allow for usable reports from huge regression tests.
948
+    """
949
+    Error tracker to allow for usable reports from huge regression tests.
850 950
 
851 951
     Best used as a result bearer from `regression_test`, this class keeps
852 952
     a simple in-memory "database" of errors seen during the regression
@@ -861,13 +961,14 @@ class Tracker(dict):
861 961
             a dict) that caused the error.
862 962
 
863 963
             If boolean value of the result is False, the object is thrown away
864
-            and nothing happen.  Otherwise, its string value is used as a key
964
+            and nothing happens.  Otherwise, its string value is used as a key
865 965
             under which the argument set is saved.
866 966
 
867
-            As you can see, the string is supposed to be ''as deterministic
868
-            as possible'', i.e. it should provide as little information
869
-            about the error as is necessary.  Do not include any timestamps
870
-            or "volatile" values.
967
+            The string interpretation of the result is supposed to be
968
+            "as deterministic as possible", i.e. it should provide only
969
+            necessary information about the error:  do not include any
970
+            timestamps or "volatile" values such as PID's, version numbers
971
+            or tempfile names.
871 972
 
872 973
          3. At final stage, you can retrieve statistics as how many (distinct)
873 974
             errors have been recorded, what was the duration of the whole test,
@@ -897,21 +998,29 @@ class Tracker(dict):
897 998
         self.driver_stats = {}
898 999
 
899 1000
     def _csv_fname(self, errstr, prefix):
900
-        """Format name of file for this error string"""
1001
+        """
1002
+        Format name of file for this error string
1003
+        """
901 1004
         return '%s/%s.csv' % (prefix, self._eid(errstr))
902 1005
 
903 1006
     def _eid(self, errstr):
904
-        """Return EID for the error string (first 7 chars of SHA1)."""
1007
+        """
1008
+        Return EID for the error string (first 7 chars of SHA1).
1009
+        """
905 1010
         return hashlib.sha1(errstr).hexdigest()[:7]
906 1011
 
907 1012
     def _insert(self, errstr, argset):
908
-        """Insert the argset into DB."""
1013
+        """
1014
+        Insert the argset into DB.
1015
+        """
909 1016
         if errstr not in self._db:
910 1017
             self._db[errstr] = []
911 1018
         self._db[errstr].append(argset)
912 1019
 
913 1020
     def _format_error(self, errstr, max_aa=0):
914
-        """Format single error for output."""
1021
+        """
1022
+        Format single error for output.
1023
+        """
915 1024
         argsets_affected = self._db[errstr]
916 1025
         num_aa = len(argsets_affected)
917 1026
 
@@ -934,11 +1043,15 @@ class Tracker(dict):
934 1043
     #
935 1044
 
936 1045
     def errors_found(self):
937
-        """Return number of non-distinct errors in db."""
1046
+        """
1047
+        Return number of non-distinct errors in db.
1048
+        """
938 1049
         return bool(self._db)
939 1050
 
940 1051
     def format_report(self, max_aa=0):
941
-        """Return complete report formatted as string."""
1052
+        """
1053
+        Return complete report formatted as string.
1054
+        """
942 1055
         error_list = "\n".join([self._format_error(e, max_aa=max_aa)
943 1056
                                 for e in self._db])
944 1057
         return ("Found %(total_errors)s (%(distinct_errors)s distinct) errors"
@@ -948,16 +1061,20 @@ class Tracker(dict):
948 1061
                 + "\n\n" + error_list)
949 1062
 
950 1063
     def getstats(self):
951
-        """Return basic and driver stats
1064
+        """
1065
+        Return basic and driver stats
1066
+
1067
+        Returns dictionary with following values:
952 1068
 
953
-            argsets_done - this should must be raised by outer code,
954
-                           once per each unique argset
955
-            tests_done   - how many times Tracker.update() was called
956
-            distinct_errors - how many distinct errors (same `str(error)`)
1069
+            'tests_done' - how many times Tracker.update() was called
1070
+
1071
+            'distinct_errors' - how many distinct errors (same `str(error)`)
957 1072
                            were seen by Tracker.update()
958
-            total_errors - how many times `Tracker.update()` saw an
1073
+
1074
+            'total_errors' - how many times `Tracker.update()` saw an
959 1075
                            error, i.e. how many argsets are in DB
960
-            time         - how long since init (seconds)
1076
+
1077
+            'time'       - how long since init (seconds)
961 1078
         """
962 1079
 
963 1080
         def total_errors():
@@ -978,7 +1095,8 @@ class Tracker(dict):
978 1095
         return stats
979 1096
 
980 1097
     def update(self, error, argset):
981
-        """Update tracker with test result.
1098
+        """
1099
+        Update tracker with test result.
982 1100
 
983 1101
         If `bool(error)` is true, it is considered error and argset
984 1102
         is inserted to DB with `str(error)` as key.  This allows for later
@@ -990,7 +1108,9 @@ class Tracker(dict):
990 1108
             self._insert(errstr, argset)
991 1109
 
992 1110
     def write_stats_csv(self, fname):
993
-        """Write stats to a simple one row (plus header) CSV."""
1111
+        """
1112
+        Write stats to a simple one row (plus header) CSV.
1113
+        """
994 1114
         stats = self.getstats()
995 1115
         colnames = sorted(stats.keys())
996 1116
         with open(fname, 'a') as fh:
@@ -999,12 +1119,14 @@ class Tracker(dict):
999 1119
             cw.writerow(stats)
1000 1120
 
1001 1121
     def write_args_csv(self, prefix=''):
1002
-        """Write out a set of CSV files, one per distinctive error.
1122
+        """
1123
+        Write out a set of CSV files, one per distinctive error.
1003 1124
 
1004 1125
         Each CSV is named with error EID (first 7 chars of SHA1) and lists
1005 1126
         all argument sets affected by this error.  This is supposed to make
1006 1127
         easier to further analyse impact and trigerring values of errors,
1007
-        perhaps using a table processor software."""
1128
+        perhaps using a table processor software.
1129
+        """
1008 1130
 
1009 1131
         def get_all_colnames():
1010 1132
             cn = {}
@@ -1028,13 +1150,18 @@ def dataMatch(pattern, data):
1028 1150
 
1029 1151
     Supports lists, dictionaries and scalars (int, float, string).
1030 1152
 
1031
-    For scalars, simple `==` is used.  Lists are converted to sets and
1032
-    "to match" means "to have a matching subset (e.g. `[1, 2, 3, 4]`
1033
-    matches `[3, 2]`).  Both lists and dictionaries are matched recursively.
1153
+    For scalars, simple `==` is used.
1154
+
1155
+    Lists are converted to sets and "to match" means "to have a matching
1156
+    subset (e.g. `[1, 2, 3, 4]` matches `[3, 2]`).
1157
+
1158
+    Both lists and dictionaries are matched recursively.
1034 1159
     """
1035 1160
 
1036 1161
     def listMatch(pattern, data):
1037
-        """Match list-like objects"""
1162
+        """
1163
+        Match list-like objects
1164
+        """
1038 1165
         assert all([hasattr(o, 'append') for o in [pattern, data]])
1039 1166
         results = []
1040 1167
         for pv in pattern:
@@ -1045,7 +1172,9 @@ def dataMatch(pattern, data):
1045 1172
         return all(results)
1046 1173
 
1047 1174
     def dictMatch(pattern, data):
1048
-        """Match dict-like objects"""
1175
+        """
1176
+        Match dict-like objects
1177
+        """
1049 1178
         assert all([hasattr(o, 'iteritems') for o in [pattern, data]])
1050 1179
         results = []
1051 1180
         try:
@@ -1068,13 +1197,16 @@ def dataMatch(pattern, data):
1068 1197
 
1069 1198
 
1070 1199
 def jsDump(data):
1071
-    """A human-readable JSON dump."""
1200
+    """
1201
+    A human-readable JSON dump.
1202
+    """
1072 1203
     return json.dumps(data, sort_keys=True, indent=4,
1073 1204
                       separators=(',', ': '))
1074 1205
 
1075 1206
 
1076 1207
 def jsDiff(dira, dirb, namea="A", nameb="B", chara="a", charb="b"):
1077
-    """JSON-based human-readable diff of two data structures.
1208
+    """
1209
+    JSON-based human-readable diff of two data structures.
1078 1210
 
1079 1211
     '''BETA''' version.
1080 1212
 
@@ -1235,33 +1367,31 @@ def jsDiff(dira, dirb, namea="A", nameb="B", chara="a", charb="b"):
1235 1367
 
1236 1368
 
1237 1369
 class Cartman:
1238
-    """Create argument sets from ranges (or ay iterators) of values.
1370
+    """
1371
+    Create argument sets from ranges (or ay iterators) of values.
1239 1372
 
1240 1373
     This class is to enable easy definition and generation of dictionary
1241
-    argument  sets using Cartesian product.  You only need to define:
1242
-
1243
-     *  structure of argument set (can be more than just flat dict)
1374
+    argument sets using Cartesian product.
1244 1375
 
1245
-     *  ranges, or arbitrary iterators of values on each "leaf" of the
1246
-        argument set
1376
+    To use Cartman iterator, you need to define structure of an argument
1377
+    set.  Argument set--typically a dictionary--is a set of values that
1378
+    together constitute a test case.  Within the argument set, values
1379
+    will change from test case to test case, so for each changing value,
1380
+    you will also need to define range of values you want to test on.
1247 1381
 
1248
-    Since there is expectation that any argument can have any kind of values
1249
-    even another iterables, the pure logic "iterate it if you can"
1250
-    is insufficient.  Instead, definition is divided in two parts:
1382
+    Cartman initiator expects following arguments:
1251 1383
 
1252
-     *  scheme, which is a "prototype" of a final argument set, except
1253
-        that for each value that will change, a `Cartman.Iterable`
1254
-        sentinel is used.  For each leaf that is constant, `Cartman.Scalar`
1255
-        is used
1384
+     *  *scheme*, which is a "prototype" of a final argument set, except
1385
+        that values are replaced by either `Cartman.Iterable` if the
1386
+        value is changing from test case to another, and `Cartman.Scalar`
1387
+        if the value is constant.
1256 1388
 
1257
-     *  source, which has the same structure, except that where in scheme
1258
-        is `Iterable`, an iterable object is expected, whereas in places
1259
-        where `Scalar` is used, a value is assigned that does not change
1260
-        during iteration.
1389
+     *  *source*, which has the same structure, except that where in scheme
1390
+        is `Cartman.Iterable`, the source has an iterable.  Where scheme has
1391
+        `Cartman.Scalar`, the source can have any value.
1261 1392
 
1262
-    Finally, when such instance is used in loop, argument sets are generated
1263
-    uising Cartesian product of each iterable found.  This allows for
1264
-    relatively easy definition of complex scenarios.
1393
+    Finally, when Cartman instance is used in loop, it uses Cartesian product
1394
+    in order to generate argument sets.
1265 1395
 
1266 1396
     Consider this example:
1267 1397