0.2.12 • Published 8 months ago

@fedeghe/testone v0.2.12

Weekly downloads
-
License
MIT
Repository
github
Last release
8 months ago

Coverage Status

@fedeghe/testone (v. 0.2.12)

Quickly test performance and correctness of one or more functions against input/output data.

// factorial implementation one
const factorialRecursive = n => {
    if (n === 1) return 1;
    else return n * factorialRecursive(n - 1);
}

// factorial implementation two
const factorialIterative = n => {
    let r = n;
    while (n > 1) r *= --n;
    return r
};

// factorial implementation three
const factorialCache = []
const factorialMemoized = n => {
    if (!factorialCache[n]) {
  	    factorialCache[n] = n <= 1 ? 1 : n * factorialMemoized(n - 1);
    }
    return factorialCache[n];
}
 
/**
 * Run the test
 */
const res = await testone([{
        in: [20],
        out: 2432902008176640000
    }, {
        in: [21],
        out: () => 2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20*21
    }, {
        in: ({iteration}) => [iteration+1],
        out: ({iteration}) => {let r = 1, i = iteration + 1; while(i > 0)r *= i--; return r;}
    }],
    [factorialRecursive, factorialIterative, factorialMemoized]
)

assert(
...

where:

  • 1st parameter: an array of object literal keyed as follows:

    • in keyed element which can be either
      • array for the function inputs
      • a function supposed to return an array to be used as input values (invoked passing {benchIndex, iteration})
    • out keyed element which can be either
      • a static value
      • a function invoked passing {received, benchIndex, iteration} supposed to return the expected output
    • matcher
      by default testone compares the expected output with the result using the exact match of the parts stringifycation:
      JSON.stringify(expected) === JSON.stringify(received)
      but in some cases where more flexibility is needed for a specific bechmark element, with this option is possible to override the matching function (which is anyway expected to return a boolean), e.g.:
      matcher: ({expected, received}) => received.length === expected.length 
  • 2nd parameter: the function or the array of functions one wants to test & check
    VERY IMPORTANT: reports as of now need functions to be named; so if the suicide hero in you is urged to beat a function from _ be sure to wrap the _ function and name it before running the test. Could be something like:

    function _Clone(o){return _.clone(o))}
  • 3rd parameter:
    {
        iterations: <Integer>,
        matcher: <function>,
        metrics: <object literal containing keyed functions>,
        plugins: <Array[{fn: <function>, options: <object literal>}]>
    }  
    more info below about the 3rd and 4th optional parameters.

What out?

  • check of the correctness
  • some relevant numerical performance informations

    {
        "times": {
            "factorialRecursive": {
                "raw": {
                    "single": 0.0028,
                    "total": 28
                },
                "withLabel": {
                    "single": "2.8 µs",
                    "total": "28 ms"
                }
            },
            "factorialIterative": {
                "raw": {
                    "single": 0.0007,
                    "total": 7
                },
                "withLabel": {
                    "single": "700 ns",
                    "total": "7 ms"
                }
            },
            "factorialMemoized": {
                "raw": {
                    "single": 0.0006,
                    "total": 6
                },
                "withLabel": {
                    "single": "600 ns",
                    "total": "6 ms"
                }
            }
        },
        "mem": {
            "factorialRecursive": {
                "raw": {
                    "single": 101.2392,
                    "total": 1012392
                },
                "withLabel": {
                    "single": "101.2392 B",
                    "total": "988.6641 KB"
                }
            },
            "factorialIterative": {
                "raw": {
                    "single": 81.624,
                    "total": 816240
                },
                "withLabel": {
                    "single": "81.624 B",
                    "total": "797.1094 KB"
                }
            },
            "factorialMemoized": {
                "raw": {
                    "single": 114.0096,
                    "total": 1140096
                },
                "withLabel": {
                    "single": "114.0096 B",
                    "total": "1.0873 MB"
                }
            }
        },
        "ops": {
            "factorialRecursive": 357142.85714285716,
            "factorialIterative": 1428571.4285714286,
            "factorialMemoized": 1666666.6666666667
        },
        "passing": true,
        "report": {
            "factorialRecursive": true,
            "factorialIterative": true,
            "factorialMemoized": true
        },
        "metrics": null,
        "pluginsResults": {}
    }
    {
        "times": {},
        "mem": {},
        "ops": {},
        "passing": false,
        "report": {
            "factorialRecursive": [
                {
                    "passing": true,
                    "time": 6
                },
                {
                    "passing": false,
                    "time": 0,
                    "err": {
                    "ioIndex": 1,
                    "received": 51090942171709440000,
                    "expected": 4865804016353280000
                    }
                },
                {
                    "passing": true,
                    "time": 7
                }
            ],
            "factorialIterative": [
                {
                    "passing": true,
                    "time": 4
                },
                {
                    "passing": false,
                    "time": 0,
                    "err": {
                    "ioIndex": 1,
                    "received": 51090942171709440000,
                    "expected": 4865804016353280000
                    }
                },
                {
                    "passing": true,
                    "time": 0
                }
            ],
            "factorialMemoized": [
                {
                    "passing": true,
                    "time": 1
                },
                {
                    "passing": false,
                    "time": 0,
                    "err": {
                    "ioIndex": 1,
                    "received": 51090942171709440000,
                    "expected": 4865804016353280000
                    }
                },
                {
                    "passing": true,
                    "time": 4
                }
            ]
        },
        "metrics": null,
        "pluginsResults": {}
    }

Options

As third parameter we can pass a literal object containing few additional things that might be usefull in some cases:

matcher

This works exactly as in the case of the single benchmark but the matcher will be used globally, still the single case matcher can be overridden.

iterations

To get a more accurate times & memory measurerements by default testone runs each function 1k times; clearly enough this could not fit every cases (exactly as above). For this reason is possible to specify in the third options literal object an integer parameter keyed iterations

metrics

when provided in the options the result will contain some additional data one might want to compute out of the results:
for example a mixed indication fo the memory consumption and time spent in one single value:

{ // in metrics
    /* will be invoked passing 
    {
        time: { single, total },
        mem: { single, total },
        pluginsResults: {// see next section }
    }
    */
    aLabel: ({time: {single: time}, mem: {single: mem}}) => time * mem
    operationsPerSecond: ({ops}}) => ops
}

and now in the returned metrics object we'll find for each metric something like:

"aLabel": {
    "factorialRecursive": 11.053367999999999,
    "factorialIterative": 1.42704,
    "factorialMemoized": 4.787640000000001
}

plugins

One can write a plugin in 2 minutes (when relying on some library for the heavy lifting) to do much more. A trivial example can clarify better than any tl;dr.

Available plugins ?

I just wrote one:

... more are coming, anyway, create your is straighforward,

Plain plugin example

Suppose we want to use a library that can crunch our strategies code and we find one possible solution: a fantomatic idothat library (our heavy lifter toward the 2 minutes goal).

We can easily get

  • the idothat results for each strategy directly in the testone output
  • consume the results also in the metrics functions.
    import idothat from 'idothat'

// every plugin must return a Promise const pleaseDoThatForMe = ({source, options}) => Promise.resolve(idothat(source)) // resolve with {info: 'done'} /**

  • .
  • ...
  • . / const res = await testone(benchs, fns, { plugins: [{ fn: pleaseDoThatForMe, options: {/ here the options you want to be passed to the adapter / plugin / }, resultsLabel: 'pleaseDoThatForMe', /** some plugins export a named function; e.g. testone-complexity-plugin exports a function named 'complex' but we could import is with another name in the case of pleaseDoThatForMe it is not a problem cause it is the real name but in the case of an external plugin the name differs and there is no cuik way to know the real name the point is that this hidden name is the one we need to know when destructuring the plugin results in the metrics (inside pluginsResults). This resultLabel allows to override the label that will be used shaping the object that will be passed to the metric functions the testone-complexity-plugin readme example clarifies that better * / skipReport: true // this will prevent the whole result of the plugin // to be added in the output first-level pluginsResults }], metrics: { cyclocplx: ({ pluginsResults: { pleaseDoThatForMe: { info } }, time: { single: time } }) => ${info} in ${time}, fx: ({ mem: {single: mem}, time: {single: time} }) => time mem
    } })

🤟 last build on 12/9/2023

0.2.12

8 months ago

0.2.11

8 months ago

0.2.10

8 months ago

0.2.9

8 months ago

0.2.7

1 year ago

0.2.6

1 year ago

0.2.8

1 year ago

0.2.3

1 year ago

0.2.5

1 year ago

0.2.4

1 year ago

0.2.2

1 year ago

0.2.1

1 year ago

0.1.10

1 year ago

0.1.2

1 year ago

0.2.0

1 year ago

0.1.8

1 year ago

0.1.7

1 year ago

0.1.9

1 year ago

0.1.4

1 year ago

0.1.3

1 year ago

0.1.6

1 year ago

0.0.23

1 year ago

0.0.24

1 year ago

0.0.25

1 year ago

0.0.30

1 year ago

0.0.31

1 year ago

0.1.0

1 year ago

0.1.1

1 year ago

0.0.26

1 year ago

0.0.27

1 year ago

0.0.28

1 year ago

0.0.29

1 year ago

0.0.20

1 year ago

0.0.21

1 year ago

0.0.10

1 year ago

0.0.22

1 year ago

0.0.11

1 year ago

0.0.12

1 year ago

0.0.13

1 year ago

0.0.14

1 year ago

0.0.15

1 year ago

0.0.9

1 year ago

0.0.16

1 year ago

0.0.8

1 year ago

0.0.17

1 year ago

0.0.18

1 year ago

0.0.19

1 year ago

0.0.5

2 years ago

0.0.7

2 years ago

0.0.6

2 years ago

0.0.4

2 years ago

0.0.3

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago