From d35495540df2b6d3ba16c84ce21627d9dbae000c Mon Sep 17 00:00:00 2001
From: Luke Shumaker <lukeshu@lukeshu.com>
Date: Fri, 10 Feb 2023 23:38:26 -0700
Subject: Fuzz for equivalence between stdlib and lowmemjson

---
 compat/json/equiv_test.go                          | 160 +++++++++++++++++++++
 .../json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/57365320c0968611  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/96aac43014471adc  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6  |   2 +
 .../json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f  |   2 +
 16 files changed, 190 insertions(+)
 create mode 100644 compat/json/equiv_test.go
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/57365320c0968611
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/96aac43014471adc
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6
 create mode 100644 compat/json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f

(limited to 'compat/json')

diff --git a/compat/json/equiv_test.go b/compat/json/equiv_test.go
new file mode 100644
index 0000000..246e4b3
--- /dev/null
+++ b/compat/json/equiv_test.go
@@ -0,0 +1,160 @@
+// Copyright (C) 2023  Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package json_test
+
+import (
+	"bytes"
+	std "encoding/json"
+	"errors"
+	"io"
+	"strconv"
+	"strings"
+	"testing"
+	"unicode/utf8"
+
+	"github.com/stretchr/testify/assert"
+
+	low "git.lukeshu.com/go/lowmemjson/compat/json"
+)
+
+func assertEquivErr(t *testing.T, stdErr, lowErr error) {
+	if (stdErr == nil) || (lowErr == nil) {
+		// Nil-equal.
+		assert.Equal(t, stdErr, lowErr)
+		return
+	}
+	switch stdErr.(type) {
+	case *std.SyntaxError:
+		if lowErr != nil {
+			stdMsg := stdErr.Error()
+			lowMsg := lowErr.Error()
+
+			// https://github.com/golang/go/issues/58680
+			if strings.HasPrefix(stdMsg, `invalid character ' ' `) &&
+				(errors.Is(lowErr, io.ErrUnexpectedEOF) || lowMsg == "unexpected end of JSON input") {
+				return
+			}
+
+			// https://github.com/golang/go/issues/58713
+			prefix := `invalid character '`
+			if stdMsg != lowMsg && strings.HasPrefix(stdMsg, prefix) && strings.HasPrefix(lowMsg, prefix) {
+				stdRune, stdRuneSize := utf8.DecodeRuneInString(stdMsg[len(prefix):])
+				lowByte := lowMsg[len(prefix)]
+				if lowByte == '\\' {
+					switch lowMsg[len(prefix)+1] {
+					case 'u':
+						lowRune, _ := strconv.ParseUint(lowMsg[len(prefix)+2:][:4], 16, 32)
+						var buf [4]byte
+						utf8.EncodeRune(buf[:], rune(lowRune))
+						lowByte = buf[0]
+					case 'U':
+						lowRune, _ := strconv.ParseUint(lowMsg[len(prefix)+2:][:8], 16, 32)
+						var buf [4]byte
+						utf8.EncodeRune(buf[:], rune(lowRune))
+						lowByte = buf[0]
+					}
+				}
+				if stdRune == rune(lowByte) {
+					lowRuneStr := lowMsg[len(prefix):]
+					lowRuneStr = lowRuneStr[:strings.IndexByte(lowRuneStr, '\'')]
+					stdMsg = prefix + lowRuneStr + stdMsg[len(prefix)+stdRuneSize:]
+					stdErr = errors.New(stdMsg)
+				}
+			}
+		}
+		// Text-equal.
+		assert.Equal(t, stdErr.Error(), lowErr.Error())
+		// TODO: Assert that they are deep-equal (but be permissive of these not being type aliases).
+	case *std.MarshalerError:
+		// Text-equal.
+		assert.Equal(t, stdErr.Error(), lowErr.Error())
+		// TODO: Assert that they are deep-equal (but be permissive of these not being type aliases).
+	default:
+		// Text-equal.
+		assert.Equal(t, stdErr.Error(), lowErr.Error())
+		// TODO: Assert that they are deep-equal.
+	}
+}
+
+func FuzzEquiv(f *testing.F) {
+	f.Fuzz(func(t *testing.T, str []byte) {
+		t.Logf("str=%q", str)
+		t.Run("HTMLEscape", func(t *testing.T) {
+			var stdOut bytes.Buffer
+			std.HTMLEscape(&stdOut, str)
+
+			var lowOut bytes.Buffer
+			low.HTMLEscape(&lowOut, str)
+
+			assert.Equal(t, stdOut.String(), lowOut.String())
+		})
+		t.Run("Compact", func(t *testing.T) {
+			var stdOut bytes.Buffer
+			stdErr := std.Compact(&stdOut, str)
+
+			var lowOut bytes.Buffer
+			lowErr := low.Compact(&lowOut, str)
+
+			assert.Equal(t, stdOut.String(), lowOut.String())
+			assertEquivErr(t, stdErr, lowErr)
+		})
+		t.Run("Indent", func(t *testing.T) {
+			var stdOut bytes.Buffer
+			stdErr := std.Indent(&stdOut, str, "»", "\t")
+
+			var lowOut bytes.Buffer
+			lowErr := low.Indent(&lowOut, str, "»", "\t")
+
+			assert.Equal(t, stdOut.String(), lowOut.String())
+			assertEquivErr(t, stdErr, lowErr)
+		})
+		t.Run("Valid", func(t *testing.T) {
+			stdValid := std.Valid(str) && utf8.Valid(str) // https://github.com/golang/go/issues/58517
+			lowValid := low.Valid(str)
+			assert.Equal(t, stdValid, lowValid)
+		})
+		t.Run("Decode-Encode", func(t *testing.T) {
+			var stdObj any
+			stdErr := std.NewDecoder(bytes.NewReader(str)).Decode(&stdObj)
+
+			var lowObj any
+			lowErr := low.NewDecoder(bytes.NewReader(str)).Decode(&lowObj)
+
+			assert.Equal(t, stdObj, lowObj)
+			assertEquivErr(t, stdErr, lowErr)
+			if t.Failed() {
+				return
+			}
+
+			var stdOut bytes.Buffer
+			stdErr = std.NewEncoder(&stdOut).Encode(stdObj)
+
+			var lowOut bytes.Buffer
+			lowErr = low.NewEncoder(&lowOut).Encode(lowObj)
+
+			assert.Equal(t, stdOut.String(), lowOut.String())
+			assertEquivErr(t, stdErr, lowErr)
+		})
+		t.Run("Unmarshal-Marshal", func(t *testing.T) {
+			var stdObj any
+			stdErr := std.Unmarshal(str, &stdObj)
+
+			var lowObj any
+			lowErr := low.Unmarshal(str, &lowObj)
+
+			assert.Equal(t, stdObj, lowObj)
+			assertEquivErr(t, stdErr, lowErr)
+			if t.Failed() {
+				return
+			}
+
+			stdOut, stdErr := std.Marshal(stdObj)
+			lowOut, lowErr := low.Marshal(lowObj)
+
+			assert.Equal(t, string(stdOut), string(lowOut))
+			assertEquivErr(t, stdErr, lowErr)
+		})
+	})
+}
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b b/compat/json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b
new file mode 100644
index 0000000..96e9e53
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/0064ebc3507e959b
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("𐠁")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1 b/compat/json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1
new file mode 100644
index 0000000..ecbe8af
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/19981bffc2abbaf1
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("A")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/57365320c0968611 b/compat/json/testdata/fuzz/FuzzEquiv/57365320c0968611
new file mode 100644
index 0000000..5aace7f
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/57365320c0968611
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("[200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae b/compat/json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae
new file mode 100644
index 0000000..a51778b
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/5cd6893f25481dae
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("0E00")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32 b/compat/json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32
new file mode 100644
index 0000000..fe2e128
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/6a6612e05e0f9e32
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\"\\uD800\"")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84 b/compat/json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84
new file mode 100644
index 0000000..e3c530f
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/77e6e971d8684f84
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\uebae")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81 b/compat/json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81
new file mode 100644
index 0000000..e8000f3
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/8727b16d337d7b81
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("00")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/96aac43014471adc b/compat/json/testdata/fuzz/FuzzEquiv/96aac43014471adc
new file mode 100644
index 0000000..9461c7a
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/96aac43014471adc
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\"\\")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f b/compat/json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f
new file mode 100644
index 0000000..1edfb06
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/9cc52906ed53ef5f
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\"")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d b/compat/json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d
new file mode 100644
index 0000000..b3c523c
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/a0b9ecf4e99fd85d
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("0.")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c b/compat/json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c
new file mode 100644
index 0000000..ca6f6f5
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/a5775dd298b90a6c
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\"\\u")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8 b/compat/json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8
new file mode 100644
index 0000000..778cc61
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/af9bedcb9e0a31e8
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("0 ")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00 b/compat/json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00
new file mode 100644
index 0000000..9644b51
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/f6b0960dd3331a00
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\"0\x85\xcd\xc0\xf3\xcb\xc1\xb3\xf2\xf5\xa4\xc1\xd40\xba\xe9\"")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6 b/compat/json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6
new file mode 100644
index 0000000..712fab9
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/fbbce5ea61559cc6
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("\U00054516")
diff --git a/compat/json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f b/compat/json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f
new file mode 100644
index 0000000..9dc2675
--- /dev/null
+++ b/compat/json/testdata/fuzz/FuzzEquiv/fd29ccbb2af92d4f
@@ -0,0 +1,2 @@
+go test fuzz v1
+[]byte("Ǒ")
-- 
cgit v1.2.3-2-g168b