Methodology
Updated

How we test our tools

How offlineutils.com tests its tools: every tool that implements a published standard is checked against that standard's own test vectors, never against its own previous output — plus what is not tested, and the bugs testing has caught.

The rule

A test that compares a tool against its own earlier output only proves the tool is consistent. It cannot tell you whether the tool was right in the first place. So every expected value in our suites comes from somewhere other than our code: the test vectors a standard publishes, another project's published test data, or arithmetic worked out by hand and written into the test. A few habits follow from that:

  • Properties where a convention is arguable.Rather than hard-coding our own answer, a test checks something that has to hold — that VLSM allocations come out the same in any order, or that the JSON formatter's layout is byte-for-byte what JSON.stringify produces on thousands of random documents.
  • Negative controls. A checker that never fails is worse than none, so new checks are shown to fail on deliberately broken input before they are trusted.
  • No skipping on our own output. An early version of one sweep skipped cases where our output looked odd, and that hid a real bug. A strange result is a finding, not something to filter out.

What is tested, and against what

Punycode (RFC 3492)

Every sample string in RFC 3492 §7.1, encoded and decoded; emoji and other astral characters handled as one code point rather than two surrogates; malformed input rejected rather than guessed at.

Expected values from: RFC 3492 §7.1 sample strings.

Covers: Punycode / IDN Converter.

Internationalised domain names (UTS #46)

Case folding, compatibility mapping and NFC normalisation; the ß and ς deviation characters; the CheckBidi and CheckJoiners rules; the URL Standard's forbidden characters; and homograph detection, including the all-Cyrillic xn--80ak6aa92e.com that browsers displayed as apple.com in 2017.

Expected values from: Unicode Technical Standard #46; RFC 5893 (Bidi rule); RFC 5892 (ContextJ); Unicode Technical Standard #39 (confusables); WHATWG URL Standard.

Covers: Punycode / IDN Converter.

Base32, Base58 and Base85

Every RFC 4648 §10 test vector for both Base32 alphabets; Bitcoin Core's own Base58 vectors and Base58Check on the genesis-block address, including a one-character typo; the ZeroMQ Z85 specification vector; and Ascii85 groups worked out by hand with the arithmetic written into the test.

Expected values from: RFC 4648 §10 test vectors; Bitcoin Core base58_encode_decode.json; ZeroMQ RFC 32 (Z85); Ascii85.

Covers: Base32 Encoder / Decoder, Base58 Encoder / Decoder, Base85 Encoder / Decoder.

Quoted-Printable and email headers

Trailing whitespace encoded, lines wrapped at 76 characters without ever splitting an escape, every byte value round-tripping, and RFC 2047 encoded words decoded with the charset each one declares.

Expected values from: RFC 2045 §6.7; RFC 2047.

Covers: Quoted-Printable Encoder / Decoder.

IPv4 and IPv6 subnetting

Usable host counts at the /31 and /32 edges, exact 128-bit counts for IPv6, canonical IPv6 text, reverse-DNS zones, and VLSM allocations that come out the same whatever order the requests arrive in.

Expected values from: RFC 3021 (/31 links); RFC 5952 (IPv6 text form); RFC 4632 (CIDR); IANA IPv4 special-purpose registry.

Covers: Subnet Calculator.

Timestamps and epochs

Nanosecond timestamps kept to the last digit, dates before 1970, and the published anchor points of the foreign epochs: Windows FILETIME, .NET ticks, Excel serial dates, Julian days, Mac HFS+ and Apple Cocoa, plus ISO 8601 week numbers across year boundaries.

Expected values from: Windows FILETIME; .NET DateTime.Ticks; ISO week date; Julian day.

Covers: Timestamp Converter.

JSON that keeps every digit

On 2,000 random documents the formatter's output is byte-for-byte what JSON.stringify would lay out, while integers past 2^53, key order, duplicate keys and string escapes all survive exactly as written.

Expected values from: RFC 8259 §6 (number interoperability).

Covers: JSON Formatter, JSON to Table Viewer, CSV ↔ JSON Converter, JWT Decoder.

Links, feed and machine-readable content

Every internal link on the site resolves, the Atom feed is well-formed XML, and every tool, glossary term and post renders to the Markdown served to AI agents.

Expected values from: RFC 4287 (Atom); llms.txt.

The exhaustive one

The domain-name converter has one more check that is too slow to run on every change: every Unicode code point, pushed through both our implementation and ICU — the library browsers use for the address bar. The last run compared 1,112,042 code points and agreed on 99.9862% of them, leaving 154 differences. All 154 are characters Unicode assigned in version 14.0 or later, which the ICU build used for comparison does not yet classify as right-to-left. None go the other way: there is no name the tool accepts that ICU refuses. The full account, including the two earlier runs and what changed between them, is in most punycode converters disagree with your browser.

What testing has found

Not every bug was found by a test, and it would be misleading to imply otherwise:

  • Timestamp Converter: 90 minutes ago read “1 hour ago” while 90 minutes ahead read “in 2 hours”, because Math.round breaks ties upwards. Found by the test suite.
  • Timestamp Converter: An unrecognised time zone threw an error during render instead of degrading, which would have blanked the tool. Found by the test suite.
  • Punycode / IDN Converter: U+FE70 maps to a space, which UTS #46 calls valid, so the tool briefly produced domain labels with a space in them. Found by the ICU sweep.
  • Punycode / IDN Converter: xn--7ba.com, which every browser resolves, was rejected, and an invisible U+200B was dropped with no warning. Found by the test suite.
  • JSON Formatter: Formatting rounded integers past 2^53 — 1753524000123456789 came back as 1753524000123456800 — reordered numeric keys and dropped duplicate keys. Found by research for our post on nanosecond timestamps.

What is not tested

The automated suite covers the logic underneath the tools, not the screens on top of them. Interface behaviour — a slider, a copy button, a drag-and-drop zone — is checked by hand, and a bug in that layer has reached production at least once: dragging the password generator's length slider threw an error until it was reported and fixed. Tools whose work is done by the browser itself, such as image conversion through the canvas API or PDF handling through pdf-lib, are tested by using them rather than by vectors, because there is no published expected output for re-encoding a JPEG.

Check it yourself

Every source above is public. Take any vector from them — an RFC 4648 example, a Base58 pair from Bitcoin Core's file, a sample string from RFC 3492 — paste it into the matching tool, and compare. Because the tools run entirely in your browser, you can do that offline, and confirm in your browser's network panel that nothing you type is sent anywhere. A tool that disagrees with a published vector is wrong, whatever this page says.