Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→tap-parse-string-to-objectnext

tap-parse-string-to-object

Parses raw Tap: string-to-object or stream-to-a-promise-of-an-object

Downloads per monthMIT Licenselibera manifesto
  • the top
  • 2022 UPDATE
  • IDEA
  • API — PARSETAP()
  • API — VERSION
  • ASYNC-USE WIT…
  • PARSER'S ALGO…

2022 Update

Since writing this program, we migrated all unit tests here (~1 million asserts if counting programmatic-ones) from tapopens in a new tab onto uvuopens in a new tab.

uvu is better maintained, has better and faster test runner.

Plus, tap’s some object string asserts can be a liability because they match partially and one might not be aware of that.

Our journey has been: tapeopens in a new tab → avaopens in a new tab → tapopens in a new tab → uvuopens in a new tab.

If you wonder, jestopens in a new tab is aimed at web projects, it’s suboptimal for npm packages-programs.

Idea

This library is a basic raw tapopens in a new tab parser, string-to-object.

It is aimed to parse Tap raw test output saved into files using tap -o.

Existing Tap parsers like tap-parseropens in a new tab are aimed at operations in the terminal; they consume piped output there and return streams.

This program is similar to JSON.parse except that it works synchronously and asynchronously, depending on what you give:

  • if you (synchronously) give it a string, it will (synchronously) output a plain object
  • if you (synchronously) give it a stream of a raw Tap string, it will output a promise of a plain object

It is not a comprehensive parser — it won’t give you test names — but it will extract the totals of all asserts and all suites.

API — parseTap()

The main function parseTap() is imported like this:

It’s a function which takes two input arguments:

The API depends on what you give as an input:

  • if you (synchronously) provide a string, you’ll (synchronously) receive a plain object
  • if you provide (synchronously) provide a stream, you’ll receive a promise of a plain object

The whole idea with streams is that raw test output files can be big — for example detergent’s raw Tap output is around 250MB, there are ~750,000 assertions there. Reading and processing such file synchronously would cripple even 8-core Intel i9 laptop. But the same file can be easily processed with streams.

However, we keep the string input as an alternative for peoples’ convenience: maybe somebody just wants to play around and doesn’t want to await? Be our guest — the synchronous result is given; just (synchronously) put a string into the inputs (promise yielding a string won’t do).

In both cases, you’ll get a result, a plain object, of a shape (Res type above):

API — version

You can import version:

Async-use, With Streams and Promises

In real-world scenarios, async is preferred so this program takes stream, and returns a promise of a plain object:

import fs from "fs";
import path from "path";
import { parseTap } from "tap-parse-string-to-object";

// notice the async IIFE, to be able to await the promise:
(async () => {
  // 1. define file's contents
  const filesContent = `TAP version 13
ok 1 - test/test.js # time=22.582ms { # Subtest: 01.01 - string input
ok 1 - 01.01.01
ok 2 - 01.01.02
1..2
ok 1 - 01.01 - string input # time=7.697ms

# Subtest: 01.02 - non-string input
ok 1 - 01.02.01
ok 2 - 01.02.02
ok 3 - 01.02.03
ok 4 - 01.02.04
ok 5 - 01.02.05
1..5
ok 2 - 01.02 - non-string input # time=2.791ms

1..2 # time=22.582ms
}

ok 2 - test/umd-test.js # time=16.522ms { # Subtest: UMD build works fine
ok 1 - should be equivalent
1..1
ok 1 - UMD build works fine # time=10.033ms

1..1 # time=16.522ms
}

1..2

# time=1816.082ms
`;

  // 2. write the test file
  await fs.writeFile(path.resolve("sampleTestStats.md"), filesContent);

  // 3. now read it again, but as a stream
  const contentsAsStream = fs.createReadStream(path.resolve("sampleTestStats.md"));

  // 4. a promise (which you await) is returned and it yields a plain object:
  const result = await parseTap(contentsAsStream);
  console.log(JSON.stringify(result, null, 4));
  // => {
  //      ok: true,
  //      assertsTotal: 8,
  //      assertsPassed: 8,
  //      assertsFailed: 0,
  //      suitesTotal: 2,
  //      suitesPassed: 2,
  //      suitesFailed: 0
  //    }
})();

Parser’s Algorithm

We wrote our own little parser. In essence, it counts all lines that (when trimmed) start with “ok” and “not ok”, with condition that those lines are consecutive and located after a line which contains a string “# Subtest”.

Each opening curly brace bumps the suite count.

The exception for all above is the chunks that get skipped — everything between line with three dashes (---) and line with three dots (...).

↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab