prevent wrapping into longer, general testing

This commit is contained in:
Arpad Ryszka 2020-11-10 00:56:25 +01:00
parent 612aa94f0a
commit b1e3ada95c
8 changed files with 1434 additions and 313 deletions

21
debug_test.go Normal file
View File

@ -0,0 +1,21 @@
package notation
import (
"fmt"
"reflect"
"testing"
)
func TestDebugNode(t *testing.T) {
const expect = `"foobarbaz"`
o := "foobarbaz"
n := reflectValue(none, reflect.ValueOf(o))
s := fmt.Sprint(n)
if s != expect {
t.Fatalf(
"failed to get debug string of node, got: %s, expected: %s",
s,
expect,
)
}
}

51
env_test.go Normal file
View File

@ -0,0 +1,51 @@
package notation
import (
"os"
"strings"
"testing"
)
func withEnv(t *testing.T, e ...string) (revert func()) {
var r []func()
revert = func() {
for i := range r {
r[i]()
}
}
revertOne := func(key, value string, existed bool) func() {
return func() {
if existed {
if err := os.Setenv(key, value); err != nil {
t.Fatal(err)
}
return
}
if err := os.Unsetenv(key); err != nil {
t.Fatal(err)
}
}
}
for i := range e {
var key, value string
p := strings.Split(e[i], "=")
key = p[0]
if len(p) > 1 {
value = p[1]
}
prev, ok := os.LookupEnv(key)
if err := os.Setenv(key, value); err != nil {
revert()
t.Fatal(err)
}
r = append(r, revertOne(key, prev, ok))
}
return
}

226
fprint.go
View File

@ -1,118 +1,160 @@
package notation package notation
import ( func unwrappable(n node) bool {
"fmt" return n.len == n.wrapLen.max &&
"io" n.len == n.fullWrap.max
)
type writer struct {
w io.Writer
n int
err error
} }
func (w *writer) write(o interface{}) { func initialize(t *int, v int) {
if w.err != nil { if *t > 0 {
return return
} }
n, err := fmt.Fprint(w.w, o) *t = v
w.n += n }
w.err = err
func max(t *int, v int) {
if *t >= v {
return
}
*t = v
} }
func nodeLen(t int, n node) node { func nodeLen(t int, n node) node {
var w int var w, f int
for i, p := range n.parts { for i, p := range n.parts {
switch part := p.(type) { switch part := p.(type) {
case string: case string:
n.len += len(part) n.len += len(part)
w += len(part) w += len(part)
f += len(part)
case node: case node:
part = nodeLen(t, part) part = nodeLen(t, part)
n.parts[i] = part n.parts[i] = part
n.len += part.len n.len += part.len
if part.len == part.wlen { if unwrappable(part) {
w += part.len
f += part.len
continue
}
if part.len == part.wrapLen.max {
w += part.len w += part.len
} else { } else {
w += part.wlen0 w += part.wrapLen.first
if w > n.wlen { initialize(&n.wrapLen.first, w)
n.wlen = w max(&n.wrapLen.max, w)
w = part.wrapLen.last
} }
if part.wlen > n.wlen { f += part.fullWrap.first
n.wlen = part.wlen initialize(&n.fullWrap.first, f)
} max(&n.fullWrap.max, f)
f = part.fullWrap.last
if n.wlen0 == 0 {
n.wlen0 = w
}
w = part.wlenLast
}
case wrapper: case wrapper:
if len(part.items) == 0 { if len(part.items) == 0 {
continue continue
} }
if w > n.wlen { initialize(&n.wrapLen.first, w)
n.wlen = w max(&n.wrapLen.max, w)
} initialize(&n.fullWrap.first, f)
max(&n.fullWrap.max, f)
if n.wlen0 == 0 { w, f = 0, 0
n.wlen0 = w
}
w = 0
for j, ni := range part.items {
ni = nodeLen(t, ni)
part.items[j] = ni
n.len += ni.len
wni := t + ni.len + len(part.suffix)
if wni > w {
w = wni
}
}
if len(part.items) > 0 {
n.len += (len(part.items) - 1) * len(part.sep) n.len += (len(part.items) - 1) * len(part.sep)
if part.mode == line {
w += (len(part.items) - 1) * len(part.sep)
} }
w = 0 for j, item := range part.items {
item = nodeLen(t, item)
part.items[j] = item
n.len += item.len
switch part.mode {
case line:
w += item.len
max(&f, item.len)
default:
wj := t + item.len + len(part.suffix)
max(&w, wj)
fj := t + item.fullWrap.max
max(&f, fj)
fj = t + item.fullWrap.last + len(part.suffix)
max(&f, fj)
} }
} }
if w > n.wlen { max(&n.wrapLen.max, w)
n.wlen = w max(&n.fullWrap.max, f)
w, f = 0, 0
}
} }
if n.wlen0 == 0 { initialize(&n.wrapLen.first, w)
n.wlen0 = w max(&n.wrapLen.max, w)
} n.wrapLen.last = w
initialize(&n.fullWrap.first, f)
n.wlenLast = w max(&n.fullWrap.max, f)
n.fullWrap.last = f
return n return n
} }
func wrapNode(t, c0, c1 int, n node) node { func wrapNode(t, c0, c1 int, n node) node {
if n.len <= c0 || n.wlen == n.len { if n.len <= c0 {
return n return n
} }
if n.len <= c1 && n.len-c0 <= n.wlen { if n.wrapLen.max >= n.len && n.fullWrap.max >= n.len {
return n
}
if n.len <= c1 && n.len-c0 <= n.wrapLen.max {
return n return n
} }
n.wrap = true n.wrap = true
if n.wlen <= c0 { cc0, cc1 := c0, c1
return n
}
for i, p := range n.parts { for i, p := range n.parts {
switch part := p.(type) { switch part := p.(type) {
case node: case node:
n.parts[i] = wrapNode(t, c0, c1, part) part = wrapNode(t, cc0, cc1, part)
n.parts[i] = part
if part.wrap {
cc0 -= part.wrapLen.last
cc1 -= part.wrapLen.last
} else {
cc0 -= part.len
cc1 -= part.len
}
case wrapper: case wrapper:
if len(part.items) > 0 {
cc0, cc1 = c0, c1
}
switch part.mode {
case line:
c0, c1 = c0-t, c1-t
var w int
for j, ni := range part.items {
if w > 0 && w+len(part.sep)+ni.len > c0 {
w = 0
part.lineWrappers = append(
part.lineWrappers,
j,
)
}
if w > 0 {
w += len(part.sep)
}
w += ni.len
}
n.parts[i] = part
c0, c1 = c0+t, c1+t
default:
for j := range part.items { for j := range part.items {
part.items[j] = wrapNode( part.items[j] = wrapNode(
t, t,
@ -123,6 +165,7 @@ func wrapNode(t, c0, c1 int, n node) node {
} }
} }
} }
}
return n return n
} }
@ -132,35 +175,60 @@ func fprint(w *writer, t int, n node) {
return return
} }
for i := 0; i < t; i++ {
w.write("\t")
}
for _, p := range n.parts { for _, p := range n.parts {
switch part := p.(type) { switch part := p.(type) {
case node: case node:
fprint(w, 0, part) fprint(w, t, part)
case wrapper: case wrapper:
if len(part.items) == 0 { if len(part.items) == 0 {
continue continue
} }
if n.wrap { if !n.wrap {
w.write("\n")
}
for i, ni := range part.items { for i, ni := range part.items {
if n.wrap { fprint(w, t, ni)
fprint(w, t+1, ni)
w.write(part.suffix)
w.write("\n")
} else {
fprint(w, 0, ni)
if i < len(part.items)-1 { if i < len(part.items)-1 {
w.write(part.sep) w.write(part.sep)
} }
} }
continue
} }
t++
switch part.mode {
case line:
var (
wi int
lineStarted bool
)
w.line(t)
for i, ni := range part.items {
if len(part.lineWrappers) > wi &&
i == part.lineWrappers[wi] {
wi++
w.line(t)
lineStarted = false
}
if lineStarted {
w.write(part.sep)
}
fprint(w, 0, ni)
lineStarted = true
}
default:
for _, ni := range part.items {
w.line(t)
fprint(w, t, ni)
w.write(part.suffix)
}
}
t--
w.line(t)
default: default:
w.write(part) w.write(part)
} }

206
fprint_test.go Normal file
View File

@ -0,0 +1,206 @@
package notation
import (
"bytes"
"errors"
"testing"
)
type failingWriter int
var errTest = errors.New("test")
func failAfter(n int) *failingWriter {
w := failingWriter(n)
return &w
}
func (w *failingWriter) Write(p []byte) (int, error) {
*w = failingWriter(int(*w) - len(p))
if *w >= 0 {
return len(p), nil
}
return len(p) + int(*w), errTest
}
func TestFailingWriter(t *testing.T) {
t.Run("single object", func(t *testing.T) {
o := struct{ fooBarBaz int }{42}
w := failAfter(9)
n, err := Fprint(w, o)
if n == 9 && err == errTest {
return
}
if n != 9 {
t.Fatalf("failed to writ the expected bytes; expected: 9, written: %d", n)
}
if err != errTest {
t.Fatalf("failed to receive the right error; expected: %v, received: %v", errTest, err)
}
})
t.Run("multiple objects, fail first", func(t *testing.T) {
o := struct{ fooBarBaz int }{42}
w := failAfter(9)
n, err := Fprint(w, o, o)
if n == 9 && err == errTest {
return
}
if n != 9 {
t.Fatalf("failed to writ the expected bytes; expected: 9, written: %d", n)
}
if err != errTest {
t.Fatalf("failed to receive the right error; expected: %v, received: %v", errTest, err)
}
})
t.Run("multiple objects, fail second", func(t *testing.T) {
o := struct{ fooBarBaz int }{42}
w := failAfter(18)
n, err := Fprint(w, o, o)
if n == 9 && err == errTest {
return
}
if n != 18 {
t.Fatalf("failed to writ the expected bytes; expected: 9, written: %d", n)
}
if err != errTest {
t.Fatalf("failed to receive the right error; expected: %v, received: %v", errTest, err)
}
})
}
func TestFprint(t *testing.T) {
t.Run("Fprint", func(t *testing.T) {
const expect = `{fooBarBaz: 42}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprint(&b, o)
if err != nil {
t.Fatal(err)
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
t.Run("Fprintw", func(t *testing.T) {
const expect = `{
fooBarBaz: 42,
}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintw(&b, o)
if err != nil {
t.Fatal(err)
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
t.Run("Fprintt", func(t *testing.T) {
const expect = `struct{fooBarBaz int}{fooBarBaz: 42}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintt(&b, o)
if err != nil {
t.Fatal(err)
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
t.Run("Fprintwt", func(t *testing.T) {
const expect = `struct{
fooBarBaz int
}{
fooBarBaz: 42,
}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintwt(&b, o)
if err != nil {
t.Fatal(err)
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
t.Run("Fprintv", func(t *testing.T) {
const expect = `struct{fooBarBaz int}{fooBarBaz: int(42)}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintv(&b, o)
if err != nil {
t.Fatal(err)
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
t.Run("Fprintv", func(t *testing.T) {
const expect = `struct{
fooBarBaz int
}{
fooBarBaz: int(42),
}`
var b bytes.Buffer
o := struct{ fooBarBaz int }{42}
defer withEnv(t, "TABWIDTH=0", "LINEWIDTH=0", "LINEWIDTH1=0")()
n, err := Fprintwv(&b, o)
if err != nil {
t.Fatal(err)
}
if n != len(expect) {
t.Fatalf("invalid write length; expected: %d, got: %d", len(expect), n)
}
if b.String() != expect {
t.Fatalf("invalid output; expected: %s, got: %s", expect, b.String())
}
})
}

View File

@ -2,6 +2,7 @@ package notation
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"os" "os"
"reflect" "reflect"
@ -18,17 +19,63 @@ const (
types types
skipTypes skipTypes
allTypes allTypes
randomMaps
) )
type wrapLen struct {
first, max, last int
}
type node struct { type node struct {
len, wlen, wlen0, wlenLast int len int
wrapLen wrapLen
fullWrap wrapLen
wrap bool wrap bool
parts []interface{} parts []interface{}
} }
type wrapMode int
const (
block wrapMode = iota
line
)
type wrapper struct { type wrapper struct {
mode wrapMode
sep, suffix string sep, suffix string
items []node items []node
lineWrappers []int
}
type writer struct {
w io.Writer
n int
err error
}
func (n node) String() string {
var b bytes.Buffer
w := &writer{w: &b}
fprint(w, 0, n)
return b.String()
}
func (w *writer) write(o interface{}) {
if w.err != nil {
return
}
n, err := fmt.Fprint(w.w, o)
w.n += n
w.err = err
}
func (w *writer) line(t int) {
w.write("\n")
for i := 0; i < t; i++ {
w.write("\t")
}
} }
func nodeOf(parts ...interface{}) node { func nodeOf(parts ...interface{}) node {
@ -55,8 +102,13 @@ func config(name string, dflt int) int {
func fprintValues(w io.Writer, o opts, v []interface{}) (int, error) { func fprintValues(w io.Writer, o opts, v []interface{}) (int, error) {
tab := config("TABWIDTH", 8) tab := config("TABWIDTH", 8)
cols0 := config("LINEWIDTH", 80-8) cols0 := config("LINEWIDTH", 80-tab)
cols1 := config("LINEWIDTH1", (cols0+8)*3/2-8) cols1 := config("LINEWIDTH1", (cols0+tab)*3/2-tab)
sortMaps := config("MAPSORT", 1)
if sortMaps == 0 {
o |= randomMaps
}
wr := &writer{w: w} wr := &writer{w: w}
for i, vi := range v { for i, vi := range v {
if wr.err != nil { if wr.err != nil {

View File

@ -46,16 +46,32 @@ func reflectPrimitive(o opts, r reflect.Value, v interface{}, suppressType ...st
return nodeOf(tn, "(", s, ")") return nodeOf(tn, "(", s, ")")
} }
func reflectNil(o opts, r reflect.Value) node { func reflectNil(o opts, groupUnnamedType bool, r reflect.Value) node {
if _, _, a := withType(o); !a { if _, _, a := withType(o); !a {
return nodeOf("nil") return nodeOf("nil")
} }
return nodeOf(reflectType(r.Type()), "(nil)") rt := r.Type()
if groupUnnamedType && rt.Name() == "" {
return nodeOf("(", reflectType(rt), ")(nil)")
}
return nodeOf(reflectType(rt), "(nil)")
} }
func reflectItems(o opts, prefix string, r reflect.Value) node { func reflectItems(o opts, prefix string, r reflect.Value) node {
items := wrapper{sep: ", ", suffix: ","} typ := r.Type()
var items wrapper
if typ.Elem().Name() == "uint8" {
items = wrapper{sep: " ", mode: line}
for i := 0; i < r.Len(); i++ {
items.items = append(
items.items,
nodeOf(fmt.Sprintf("%02x", r.Index(i).Uint())),
)
}
} else {
items = wrapper{sep: ", ", suffix: ","}
itemOpts := o | skipTypes itemOpts := o | skipTypes
for i := 0; i < r.Len(); i++ { for i := 0; i < r.Len(); i++ {
items.items = append( items.items = append(
@ -63,17 +79,18 @@ func reflectItems(o opts, prefix string, r reflect.Value) node {
reflectValue(itemOpts, r.Index(i)), reflectValue(itemOpts, r.Index(i)),
) )
} }
}
if _, t, _ := withType(o); !t { if _, t, _ := withType(o); !t {
return nodeOf(prefix, "{", items, "}") return nodeOf(prefix, "{", items, "}")
} }
return nodeOf(reflectType(r.Type()), "{", items, "}") return nodeOf(reflectType(typ), "{", items, "}")
} }
func reflectHidden(o opts, hidden string, r reflect.Value) node { func reflectHidden(o opts, hidden string, r reflect.Value) node {
if r.IsNil() { if r.IsNil() {
return reflectNil(o, r) return reflectNil(o, true, r)
} }
if _, t, _ := withType(o); !t { if _, t, _ := withType(o); !t {
@ -97,7 +114,7 @@ func reflectFunc(o opts, r reflect.Value) node {
func reflectInterface(o opts, r reflect.Value) node { func reflectInterface(o opts, r reflect.Value) node {
if r.IsNil() { if r.IsNil() {
return reflectNil(o, r) return reflectNil(o, false, r)
} }
e := reflectValue(o, r.Elem()) e := reflectValue(o, r.Elem())
@ -115,7 +132,7 @@ func reflectInterface(o opts, r reflect.Value) node {
func reflectMap(o opts, r reflect.Value) node { func reflectMap(o opts, r reflect.Value) node {
if r.IsNil() { if r.IsNil() {
return reflectNil(o, r) return reflectNil(o, true, r)
} }
var ( var (
@ -140,7 +157,10 @@ func reflectMap(o opts, r reflect.Value) node {
sn[skey] = nk sn[skey] = nk
} }
if o&randomMaps == 0 {
sort.Strings(skeys) sort.Strings(skeys)
}
for _, skey := range skeys { for _, skey := range skeys {
items.items = append( items.items = append(
items.items, items.items,
@ -161,7 +181,7 @@ func reflectMap(o opts, r reflect.Value) node {
func reflectPointer(o opts, r reflect.Value) node { func reflectPointer(o opts, r reflect.Value) node {
if r.IsNil() { if r.IsNil() {
return reflectNil(o, r) return reflectNil(o, true, r)
} }
e := reflectValue(o, r.Elem()) e := reflectValue(o, r.Elem())
@ -174,7 +194,7 @@ func reflectPointer(o opts, r reflect.Value) node {
func reflectList(o opts, r reflect.Value) node { func reflectList(o opts, r reflect.Value) node {
if r.IsNil() { if r.IsNil() {
return reflectNil(o, r) return reflectNil(o, true, r)
} }
return reflectItems(o, "[]", r) return reflectItems(o, "[]", r)
@ -221,7 +241,7 @@ func reflectString(o opts, r reflect.Value) node {
} }
func reflectStruct(o opts, r reflect.Value) node { func reflectStruct(o opts, r reflect.Value) node {
wr := wrapper{sep: ", "} wr := wrapper{sep: ", ", suffix: ","}
fieldOpts := o | skipTypes fieldOpts := o | skipTypes
rt := r.Type() rt := r.Type()
@ -249,7 +269,7 @@ func reflectStruct(o opts, r reflect.Value) node {
func reflectUnsafePointer(o opts, r reflect.Value) node { func reflectUnsafePointer(o opts, r reflect.Value) node {
if r.IsNil() { if r.IsNil() {
return reflectNil(o, r) return reflectNil(o, false, r)
} }
if _, _, a := withType(o); !a { if _, _, a := withType(o); !a {
@ -298,11 +318,9 @@ func reflectValue(o opts, r reflect.Value) node {
return reflectList(o, r) return reflectList(o, r)
case reflect.String: case reflect.String:
return reflectString(o, r) return reflectString(o, r)
case reflect.Struct:
return reflectStruct(o, r)
case reflect.UnsafePointer: case reflect.UnsafePointer:
return reflectUnsafePointer(o, r) return reflectUnsafePointer(o, r)
default: default:
return nodeOf("<invalid>") return reflectStruct(o, r)
} }
} }

View File

@ -1,13 +1,17 @@
package notation package notation
import ( import "reflect"
"reflect"
)
func reflectFuncBaseType(t reflect.Type) node { func reflectFuncBaseType(t reflect.Type) node {
isVariadic := t.IsVariadic()
args := func(num func() int, typ func(int) reflect.Type) []node { args := func(num func() int, typ func(int) reflect.Type) []node {
var t []node var t []node
for i := 0; i < num(); i++ { for i := 0; i < num(); i++ {
if i == num()-1 && isVariadic {
t = append(t, nodeOf("...", reflectType(typ(i).Elem())))
continue
}
t = append(t, reflectType(typ(i))) t = append(t, reflectType(typ(i)))
} }
@ -21,14 +25,14 @@ func reflectFuncBaseType(t reflect.Type) node {
if len(in) == 1 { if len(in) == 1 {
n.parts = append(n.parts, in[0]) n.parts = append(n.parts, in[0])
} else if len(in) > 1 { } else if len(in) > 1 {
n.parts = append(n.parts, wrapper{sep: ", ", items: in}) n.parts = append(n.parts, wrapper{sep: ", ", suffix: ",", items: in})
} }
n.parts = append(n.parts, ")") n.parts = append(n.parts, ")")
if len(out) == 1 { if len(out) == 1 {
n.parts = append(n.parts, " ", out[0]) n.parts = append(n.parts, " ", out[0])
} else if len(out) > 1 { } else if len(out) > 1 {
n.parts = append(n.parts, " (", wrapper{sep: ", ", items: out}, ")") n.parts = append(n.parts, " (", wrapper{sep: ", ", suffix: ",", items: out}, ")")
} }
return n return n
@ -103,7 +107,12 @@ func reflectStructType(t reflect.Type) node {
func reflectType(t reflect.Type) node { func reflectType(t reflect.Type) node {
if t.Name() != "" { if t.Name() != "" {
return nodeOf(t.Name()) name := t.Name()
if name == "uint8" {
name = "byte"
}
return nodeOf(name)
} }
switch t.Kind() { switch t.Kind() {
@ -121,9 +130,7 @@ func reflectType(t reflect.Type) node {
return reflectPointerType(t) return reflectPointerType(t)
case reflect.Slice: case reflect.Slice:
return reflectListType(t) return reflectListType(t)
case reflect.Struct:
return reflectStructType(t)
default: default:
return nodeOf("<invalid>") return reflectStructType(t)
} }
} }

File diff suppressed because it is too large Load Diff