npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@fedeghe/testone

v0.2.14

Published

small testing utility

Downloads

14

Readme

Coverage Status

@fedeghe/testone (v. 0.2.14)

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 18/5/2024