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

@dylibso/xtp-test

v0.0.9

Published

XTP test harness

Downloads

35

Readme

xtp-test

A JavaScript test framework for xtp / Extism plugins.

Example

// test.js
import { Test } from "@dylibso/xtp-test";

export function test() {
  // call a function from some Extism plugin (you'll link these up in the CLI command to run the test),
  // passing in some data and getting back `MemoryView`, which we convert to JSON using the `MemoryView.json`
  // method
  const res = Test.call("count_vowels", "some input").json();
  const count = res["count"];
  // assert the count of the vowels is correct, giving the test case a name (which will be shown in the CLI output)
  Test.assertEqual("count_vowels of 'some input'", count, 4);

  // create a group of tests, which will be run together. This resets the plugin before and after the group is complete.
  Test.group("count_vowels maintains state", () => {
    let accumTotal = 0;
    const expectedFinalTotal = 12;
    for (let i = 0; i < 3; i++) {
      const output = Test.call("count_vowels", "this is a test").json();
      accumTotal += output.count;
      Test.assertEqual(
        `total count increased to: ${accumTotal}`,
        accumTotal,
        4 * (i + 1),
      );
    }

    Test.assertEqual(
      "expected total reached by end of test",
      accumTotal,
      expectedFinalTotal,
    );
  });

  // this function is also an Extism plugin, so return an int32 value (non-zero returns will cause the whole test suite to fail.)
  return 0;
}

API Docs

Test is the primary entrypoint to this library. It exposes plugin calling functions and timing & assetion functions to validate expectations of plugin behavior.

export class Test {
  // call a function from the Extism plugin being tested, passing in `Input` and returning the output as `MemoryView`, which 
  // can be used to convert the type to a JavaScript native value.
  static call(funcName: string, input: Input): MemoryView { ... }

  // read the mock test input provided by the test runner, returns `MemoryView`.
  // this input is defined in an xtp.toml file, or by the --mock-input-data or --mock-input-file flags.
  static mockInput(): MemoryView { ... }
  
  // Run a test group, resetting the plugin before and after the group is run.
  static group(name: string, callback: () => void) { .. }

  // Reset the loaded plugin, clearing all state.
  static reset() { ... }

  // call a function from the Extism plugin being tested, passing in `Input` and get the number of nanoseconds spent in the function.
  static timeNanoseconds(funcName: string, input: Input): number { ... }

  // call a function from the Extism plugin being tested, passing in `Input` and get the number of seconds spent in the function.
  static timeSeconds(funcName: string, input: Input): number { ... }

  // assert that the `outcome` is true, naming the assertion with `name`, which will be used as a label in the CLI runner. The `reason` argument
  // will be used to print a message when the assertion fails, this should contain some additional information about values being compared.
  static assert(name: string, outcome: boolean, reason: string) { ... }

  // assert that `x` and `y` are equal, naming the assertion with `msg`, which will be used as a label in the CLI runner.
  static assertEqual(msg: string, x: unknown, y: unknown) { ... }
  
  // assert that `x` and `y` are not equal, naming the assertion with `msg`, which will be used as a label in the CLI runner.
  static assertNotEqual(msg: string, x: unknown, y: unknown) { ... }

  // assert that `x` is greater than `y`, naming the assertion with `msg`, which will be used as a label in the CLI runner.
  static assertGreaterThan(msg: string, x: any, y: any) { ... }

  // assert that `x` is greater than or equal to `y`, naming the assertion with `msg`, which will be used as a label in the CLI runner.
  static assertGreaterThanOrEqualTo(msg: string, x: any, y: any) { ... }
  
  // assert that `x` is less than `y`, naming the assertion with `msg`, which will be used as a label in the CLI runner.
  static assertLessThan(msg: string, x: any, y: any) { ... }
  
  // assert that `x` is less than or equal to `y`, naming the assertion with `msg`, which will be used as a label in the CLI runner.
  static assertLessThanOrEqualTo(msg: string, x: any, y: any) { ... }
}

Input is a type to represent various kinds of function call input data.

type Input = string | ArrayBuffer | object | undefined;

MemoryView wraps an Extism memory handle, allowing you to convert between multiple types without dealing directly with low-level memory access.

// Provides access to data in Extism memory
export class MemoryView extends DataView {
  ...

  // Returns true if the underlying memory handle is empty or undefined.
  isEmpty(): boolean {...}

  // Get the JSON representation of a value stored in Extism memory
  json(): any { ... }

  // Get the string representation of a value stored in Extism memory
  text(): string { ... }

  // Read bytes from Extism memory into an ArrayBuffer
  arrayBuffer(): ArrayBuffer { ... }
}

Usage

Follow the steps to compile this to WebAssembly using the Extism js-pdk toolchain, in particular, the instructions to use a bundler.

Quick steps:

1. Install extism-js compiler:

curl -O https://raw.githubusercontent.com/extism/js-pdk/main/install.sh
sh install.sh

2. Create your test script:

You need an interface file to link the xtp-list library, so create test.d.ts and paste this:

// test.d.ts
declare module "main" {
  export function test(): I32;
}

declare module "xtp:test" {
  interface harness {
    assert(name: PTR, value: I64, reason: PTR): void;
    call(func: PTR, input: PTR): PTR;
    time(func: PTR, input: PTR): I64;
    group(name: PTR): void;
    reset(): void;
    mock_input(): PTR;
  }
}

Your test will call function exports, but here we demonstrate calling our count_vowels function from an example module:

// test.js
import { Test } from "@dylibso/xtp-test";

export function test() {
  const res = Test.call("count_vowels", "some input").json();
  const count = res["count"];
  Test.assertEqual("count_vowels of 'some input'", count, 4);
  return 0;
}

3. Compile your test and interface to .wasm:

Once you bundle test.js using esbuild or something similar, you can compile your test to .wasm using extism-js:

extism-js dist/test.js -i test.d.ts -o test.wasm

4. Run the test against your plugin: Once you have your test code as a .wasm module, you can run the test against your plugin using the xtp CLI:

Install xtp

curl https://static.dylibso.com/cli/install.sh | sudo sh

Run the test suite

xtp plugin test ./plugin-*.wasm --with test.wasm --mock-host host.wasm
#               ^^^^^^^^^^^^^^^        ^^^^^^^^^             ^^^^^^^^^
#               your plugin(s)         test to run           optional mock host functions

Note: The optional mock host functions must be implemented as Extism plugins, whose exported functions match the host function signature imported by the plugins being tested.

Need Help?

Please reach out via the #xtp channel on Discord.