From 4d982e35e14a7f3620d49058d153f7e9f90c265b Mon Sep 17 00:00:00 2001 From: Kyoichiro Yamada Date: Mon, 23 Oct 2017 19:40:00 +0900 Subject: [PATCH] increase coverage --- _bin/cover.sh | 2 +- config/color.go | 39 ++++---- config/color_test.go | 74 +++++++++++++-- config/color_type.go | 2 + config/concat.go | 171 ++++++++++++++++++++++++++++++++++ config/concat_test.go | 114 +++++++++++++++++++++++ config/config.go | 66 ------------- config/label_type.go | 30 ++---- config/label_type_test.go | 25 +++++ config/load.go | 35 +++++-- config/load_test.go | 147 +++++++++++++++++++++++++++++ config/style.go | 147 ++++++----------------------- config/style_test.go | 29 ++++++ editor/editor.go | 5 +- editor/editor_test.go | 59 ++++++++++++ editor/parrot_test.go | 16 ++++ editor/regexp_flow_test.go | 35 +++++++ editor/test/label_set_test.go | 1 + editor/tty_test.go | 14 +++ 19 files changed, 768 insertions(+), 243 deletions(-) create mode 100644 config/concat.go create mode 100644 config/concat_test.go create mode 100644 config/label_type_test.go create mode 100644 config/load_test.go create mode 100644 config/style_test.go create mode 100644 editor/editor_test.go create mode 100644 editor/parrot_test.go create mode 100644 editor/regexp_flow_test.go create mode 100644 editor/test/label_set_test.go create mode 100644 editor/tty_test.go diff --git a/_bin/cover.sh b/_bin/cover.sh index 89324ea..d2b5ae1 100755 --- a/_bin/cover.sh +++ b/_bin/cover.sh @@ -12,5 +12,5 @@ done rm -f profile.cover if [ -n "${COVERALLS_TOKEN}" ]; then goveralls -coverprofile=acc.cover -repotoken=$COVERALLS_TOKEN -service=wercker.com + rm -f acc.cover fi -rm -f acc.cover diff --git a/config/color.go b/config/color.go index baa03bc..ad32539 100644 --- a/config/color.go +++ b/config/color.go @@ -26,6 +26,8 @@ type Color struct { // MarshalYAML implements Marshaler func (c Color) MarshalYAML() (interface{}, error) { switch c.Type { + case ColorTypeNone: + return "", nil case ColorTypeName: return c.Name.String(), nil case ColorType8Bit: @@ -39,6 +41,8 @@ func (c Color) MarshalYAML() (interface{}, error) { // MarshalJSON implements Marshaler func (c Color) MarshalJSON() ([]byte, error) { switch c.Type { + case ColorTypeNone: + return []byte(`""`), nil case ColorTypeName: return []byte(fmt.Sprintf(`"%s"`, c.Name.String())), nil case ColorType8Bit: @@ -145,6 +149,10 @@ func (c *Color) unmarshal(str string, unquote bool) error { str = unquoted } } + if str == "" { + c.Type = ColorTypeNone + return nil + } if err := c.unmarshalAs8Bit(str); err == nil { return nil } @@ -185,23 +193,6 @@ func atoiCore(s string) (uint64, error) { return strconv.ParseUint(s, 10, 8) } -func concatColor(a, b *Color) *Color { - if a == nil { - return b - } - return a -} - -func actualColor(c *Color) *Color { - if c == nil { - return &Color{ - Type: ColorTypeName, - Name: DefaultColor, - } - } - return c -} - var backColors = map[ColorName]aec.ANSI{ Black: aec.BlackB, Red: aec.RedB, @@ -221,9 +212,17 @@ var backColors = map[ColorName]aec.ANSI{ LightWhite: aec.LightWhiteB, } +var emptyColor aec.ANSI + +func init() { + emptyColor = aec.EmptyBuilder.ANSI +} + // B gets background ANSI color func (c *Color) B() aec.ANSI { switch c.Type { + case ColorTypeNone: + return emptyColor case ColorTypeName: b, ok := backColors[c.Name] if ok { @@ -234,7 +233,7 @@ func (c *Color) B() aec.ANSI { case ColorType24Bit: return aec.FullColorB(c.ValueR, c.ValueG, c.ValueB) } - return aec.DefaultB + return emptyColor } var frontColors = map[ColorName]aec.ANSI{ @@ -259,6 +258,8 @@ var frontColors = map[ColorName]aec.ANSI{ // F gets foreground ANSI color func (c *Color) F() aec.ANSI { switch c.Type { + case ColorTypeNone: + return emptyColor case ColorTypeName: f, ok := frontColors[c.Name] if ok { @@ -269,5 +270,5 @@ func (c *Color) F() aec.ANSI { case ColorType24Bit: return aec.FullColorF(c.ValueR, c.ValueG, c.ValueB) } - return aec.DefaultF + return emptyColor } diff --git a/config/color_test.go b/config/color_test.go index 2c22687..e8769c6 100644 --- a/config/color_test.go +++ b/config/color_test.go @@ -3,17 +3,21 @@ package config import ( "encoding/json" "fmt" + "strings" "testing" + "github.com/stretchr/testify/assert" yaml "gopkg.in/yaml.v2" ) type testStruct struct { - C Color `json:"c" yaml:"c"` + C Color `json:"c" yaml:"c,omitempty"` } func TestMarshalYAML(t *testing.T) { for exp, color := range map[string]testStruct{ + `{}`: {}, + `c: ""`: {Color{Type: ColorTypeNone}}, `c: black`: {Color{Type: ColorTypeName, Name: Black}}, `c: '#ffeedd'`: {Color{Type: ColorType24Bit, ValueR: 0xff, ValueG: 0xee, ValueB: 0xdd}}, `c: 31`: {Color{Type: ColorType8Bit, Value8: 31}}, @@ -26,7 +30,7 @@ func TestMarshalYAML(t *testing.T) { exp += "\n" // NOTE: yaml will have trailing newline act := string(buf) if exp != act { - t.Errorf("expected a marshaled color %q, but %q", exp, act) + t.Errorf("expect that a marshaled color be %q, but %q", exp, act) } } @@ -38,18 +42,19 @@ func TestMarshalYAML(t *testing.T) { func TestMarshalJSON(t *testing.T) { for exp, color := range map[string]testStruct{ + `{"c":""}`: {C: Color{Type: ColorTypeNone}}, `{"c":"black"}`: {C: Color{Type: ColorTypeName, Name: Black}}, `{"c":"#ffeedd"}`: {C: Color{Type: ColorType24Bit, ValueR: 0xff, ValueG: 0xee, ValueB: 0xdd}}, `{"c":31}`: {C: Color{Type: ColorType8Bit, Value8: 31}}, } { buf, err := json.Marshal(&color) if err != nil { - t.Errorf("failed to marshal a color %q to json with error %q", "black", err) + t.Errorf("failed to marshal a color to json with error %q", err) t.FailNow() } act := string(buf) if exp != act { - t.Errorf("expected a marshaled color %q, but %q", exp, act) + t.Errorf("expect that a marshaled color be %q, but %q", exp, act) } } @@ -62,6 +67,7 @@ func TestMarshalJSON(t *testing.T) { func TestUnmarshalYAML(t *testing.T) { const yamlTemplate = "act: %s" for value, exp := range map[string]Color{ + `""`: Color{Type: ColorTypeNone}, `0x1F`: Color{Type: ColorType8Bit, Value8: 0x1F}, `"0x1F"`: Color{Type: ColorType8Bit, Value8: 0x1F}, `25`: Color{Type: ColorType8Bit, Value8: 25}, @@ -80,10 +86,21 @@ func TestUnmarshalYAML(t *testing.T) { continue } if obj.Act != exp { - t.Errorf("expected a marshaled color %#v, but %#v", exp, obj.Act) + t.Errorf("expect that a marshaled color be %#v, but %#v", exp, obj.Act) } } + var obj struct { + Act *Color `yaml:"act"` + } + err := yaml.Unmarshal([]byte{}, &obj) + if err != nil { + t.Errorf("failed to unmarshal a empty color as yaml with error %q", err) + } + if obj.Act != nil { + t.Errorf("expect that a marshaled color be nil, but %#v", obj.Act) + } + for _, value := range []string{ `{}`, `[]`, @@ -105,6 +122,7 @@ func TestUnmarshalYAML(t *testing.T) { func TestUnmarshalJSON(t *testing.T) { const jsonTemplate = `{"act":%s}` for value, exp := range map[string]Color{ + `""`: Color{Type: ColorTypeNone}, `"0x1F"`: Color{Type: ColorType8Bit, Value8: 0x1F}, `25`: Color{Type: ColorType8Bit, Value8: 25}, `"red"`: Color{Type: ColorTypeName, Name: Red}, @@ -120,7 +138,7 @@ func TestUnmarshalJSON(t *testing.T) { continue } if obj.Act != exp { - t.Errorf("expected a marshaled color %#v, but %#v", exp, obj.Act) + t.Errorf("expect that a marshaled color be %#v, but %#v", exp, obj.Act) } } for _, value := range []string{ @@ -140,3 +158,47 @@ func TestUnmarshalJSON(t *testing.T) { } } } + +func TestB(t *testing.T) { + const esc = "\x1b[" + for _, c := range []Color{ + {Type: ColorTypeName, Name: Black}, + {Type: ColorType8Bit, Value8: 11}, + {Type: ColorType24Bit, ValueR: 33, ValueG: 22, ValueB: 11}, + } { + // B() func must return Back Color (formed ESC+[+3x) + if !strings.HasPrefix(c.B().String(), esc+"4") { + t.Errorf("invalid front color: %s", strings.TrimPrefix(c.B().String(), esc)) + } + } + + for _, c := range []Color{ + {}, + {Type: ColorTypeName, Name: "invalidColor"}, + } { + c := c + assert.Equal(t, "", c.B().String()) + } +} + +func TestF(t *testing.T) { + const esc = "\x1b[" + for _, c := range []Color{ + {Type: ColorTypeName, Name: Black}, + {Type: ColorType8Bit, Value8: 11}, + {Type: ColorType24Bit, ValueR: 33, ValueG: 22, ValueB: 11}, + } { + // F() func must return Front Color (formed ESC+[+3x) + if !strings.HasPrefix(c.F().String(), esc+"3") { + t.Errorf("invalid front color: %s", strings.TrimPrefix(c.F().String(), esc)) + } + } + + for _, c := range []Color{ + {}, + {Type: ColorTypeName, Name: "invalidColor"}, + } { + c := c + assert.Equal(t, "", c.F().String()) + } +} diff --git a/config/color_type.go b/config/color_type.go index 2faff23..c317772 100644 --- a/config/color_type.go +++ b/config/color_type.go @@ -4,6 +4,8 @@ package config type ColorType string const ( + // ColorTypeNone defines empty + ColorTypeNone = ColorType("none") // ColorType8Bit defines 8-bit (256) colors ColorType8Bit = ColorType("8bit") // ColorType24Bit defines 24-bit (R: 8bit + G: 8bit + B: 8bit ; full) colors diff --git a/config/concat.go b/config/concat.go new file mode 100644 index 0000000..e5e13c1 --- /dev/null +++ b/config/concat.go @@ -0,0 +1,171 @@ +package config + +import "github.com/wacul/ptr" + +func concatInt(a, b *int) *int { + if a == nil { + if b == nil { + return nil + } + f := *b + return &f + } + return a +} + +func actualInt(b *int) *int { + if b == nil { + return ptr.Int(0) + } + return b +} + +func concatConfig(base, other *Config) *Config { + if base == nil { + if other == nil { + return nil + } + base = &Config{} + } + if other == nil { + other = &Config{} + } + return &Config{ + LabelType: concatLabelType(base.LabelType, other.LabelType), + BuildStyle: concatStyle(base.BuildStyle, other.BuildStyle), + StartStyle: concatStyle(base.StartStyle, other.StartStyle), + PassStyle: concatStyle(base.PassStyle, other.PassStyle), + FailStyle: concatStyle(base.FailStyle, other.FailStyle), + SkipStyle: concatStyle(base.SkipStyle, other.SkipStyle), + FileStyle: concatStyle(base.FileStyle, other.FileStyle), + LineStyle: concatStyle(base.LineStyle, other.LineStyle), + CoverThreshold: concatInt(base.CoverThreshold, other.CoverThreshold), + CoveredStyle: concatStyle(base.CoveredStyle, other.CoveredStyle), + UncoveredStyle: concatStyle(base.UncoveredStyle, other.UncoveredStyle), + Removals: append(base.Removals, other.Removals...), + } +} + +func actualConfig(config *Config) *Config { + if config == nil { + config = &Config{} + } + return &Config{ + LabelType: actualLabelType(config.LabelType), + BuildStyle: actualStyle(config.BuildStyle), + StartStyle: actualStyle(config.StartStyle), + PassStyle: actualStyle(config.PassStyle), + FailStyle: actualStyle(config.FailStyle), + SkipStyle: actualStyle(config.SkipStyle), + FileStyle: actualStyle(config.FileStyle), + LineStyle: actualStyle(config.LineStyle), + CoverThreshold: actualInt(config.CoverThreshold), + CoveredStyle: actualStyle(config.CoveredStyle), + UncoveredStyle: actualStyle(config.UncoveredStyle), + Removals: config.Removals, + } +} + +func concatColor(a, b *Color) *Color { + if a == nil { + return b + } + return a +} + +func actualColor(c *Color) *Color { + if c == nil { + return &Color{ + Type: ColorTypeNone, + } + } + return c +} + +func concatLabelType(base, other *LabelType) *LabelType { + if base == nil { + if other == nil { + return nil + } + return other + } + return base +} + +func actualLabelType(t *LabelType) *LabelType { + if t == nil { + l := LabelTypeLong + return &l + } + return t +} + +func concatStyle(base, other *Style) *Style { + if base == nil { + if other == nil { + return nil + } + base = &Style{} + } + if other == nil { + other = &Style{} + } + return &Style{ + Hide: concatBool(base.Hide, other.Hide), + Bold: concatBool(base.Bold, other.Bold), + Faint: concatBool(base.Faint, other.Faint), + Italic: concatBool(base.Italic, other.Italic), + Underline: concatBool(base.Underline, other.Underline), + BlinkSlow: concatBool(base.BlinkSlow, other.BlinkSlow), + BlinkRapid: concatBool(base.BlinkRapid, other.BlinkRapid), + Inverse: concatBool(base.Inverse, other.Inverse), + Conceal: concatBool(base.Conceal, other.Conceal), + CrossOut: concatBool(base.CrossOut, other.CrossOut), + Frame: concatBool(base.Frame, other.Frame), + Encircle: concatBool(base.Encircle, other.Encircle), + Overline: concatBool(base.Overline, other.Overline), + Foreground: concatColor(base.Foreground, other.Foreground), + Background: concatColor(base.Background, other.Background), + } +} + +func actualStyle(s *Style) *Style { + if s == nil { + s = &Style{} + } + return &Style{ + Hide: actualBool(s.Hide), + Bold: actualBool(s.Bold), + Faint: actualBool(s.Faint), + Italic: actualBool(s.Italic), + Underline: actualBool(s.Underline), + BlinkSlow: actualBool(s.BlinkSlow), + BlinkRapid: actualBool(s.BlinkRapid), + Inverse: actualBool(s.Inverse), + Conceal: actualBool(s.Conceal), + CrossOut: actualBool(s.CrossOut), + Frame: actualBool(s.Frame), + Encircle: actualBool(s.Encircle), + Overline: actualBool(s.Overline), + Foreground: actualColor(s.Foreground), + Background: actualColor(s.Background), + } +} + +func concatBool(a, b *bool) *bool { + if a == nil { + if b == nil { + return nil + } + f := *b + return &f + } + return a +} + +func actualBool(b *bool) *bool { + if b == nil { + return ptr.Bool(false) + } + return b +} diff --git a/config/concat_test.go b/config/concat_test.go new file mode 100644 index 0000000..5c87d19 --- /dev/null +++ b/config/concat_test.go @@ -0,0 +1,114 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/wacul/ptr" +) + +func TestConcatConfig(t *testing.T) { + assert.Nil(t, concatConfig(nil, nil), "concat(NIL,NIL)") + assert.Equal(t, &Config{CoverThreshold: ptr.Int(1)}, concatConfig(&Config{CoverThreshold: ptr.Int(1)}, nil), "concat(VAL, NIL)") + assert.Equal(t, &Config{CoverThreshold: ptr.Int(1)}, concatConfig(&Config{CoverThreshold: ptr.Int(1)}, &Config{CoverThreshold: ptr.Int(2)}), "concat(VAL, VAL)") + assert.Equal(t, &Config{CoverThreshold: ptr.Int(2)}, concatConfig(nil, &Config{CoverThreshold: ptr.Int(2)}), "concat(NIL, VAL)") +} + +func TestActualConfig(t *testing.T) { + t.Run("initial actual config", func(t *testing.T) { + act := actualConfig(nil) + if act == nil { + t.Error("expected that the actual config never be nil, but not") + t.FailNow() + } + if act.BuildStyle == nil { + t.Error("expect that any property of the actual config never be nil, but not") + } + }) + + t.Run("actual config will save a value", func(t *testing.T) { + act := actualConfig(&Config{ + CoverThreshold: ptr.Int(10), + }) + if act == nil { + t.Error("expect that the actual config never be nil, but not") + t.FailNow() + } + if act.CoverThreshold == nil { + t.Error("expect that any property of the actual config never be nil, but not") + t.FailNow() + } + if *act.CoverThreshold != 10 { + t.Error("expect that the actual config will save a value, but not") + } + }) + +} + +func TestConcatColor(t *testing.T) { + color := func(n uint8) *Color { + return &Color{Type: ColorType8Bit, Value8: n} + } + assert.Nil(t, concatColor(nil, nil), "concat(NIL,NIL)") + assert.Equal(t, color(1), concatColor(color(1), nil), "concat(VAL, NIL)") + assert.Equal(t, color(1), concatColor(color(1), color(2)), "concat(VAL, VAL)") + assert.Equal(t, color(2), concatColor(nil, color(2)), "concat(NIL, VAL)") +} + +func TestActualColor(t *testing.T) { + color := func(n uint8) *Color { + return &Color{Type: ColorType8Bit, Value8: n} + } + assert.Equal(t, &Color{Type: ColorTypeNone}, actualColor(nil), "actual(NIL)") + assert.Equal(t, color(1), actualColor(color(1)), "actual(VAL)") +} + +func TestConcatLabelType(t *testing.T) { + foo := (*LabelType)(ptr.String("foo")) + bar := (*LabelType)(ptr.String("bar")) + assert.Nil(t, concatLabelType(nil, nil), "concat(NIL,NIL)") + assert.Equal(t, foo, concatLabelType(foo, nil), "concat(VAL, NIL)") + assert.Equal(t, foo, concatLabelType(foo, bar), "concat(VAL, VAL)") + assert.Equal(t, bar, concatLabelType(nil, bar), "concat(NIL, VAL)") +} + +func TestActualLabelType(t *testing.T) { + long := LabelTypeLong + foo := (*LabelType)(ptr.String("foo")) + assert.Equal(t, &long, actualLabelType(nil), "actual(NIL)") + assert.Equal(t, foo, actualLabelType(foo), "actual(VAL)") +} + +func TestConcatStyle(t *testing.T) { + style := func(n uint8) *Style { + return &Style{Foreground: &Color{Type: ColorType8Bit, Value8: n}} + } + assert.Nil(t, concatStyle(nil, nil), "concat(NIL,NIL)") + assert.Equal(t, style(1), concatStyle(style(1), nil), "concat(VAL, NIL)") + assert.Equal(t, style(1), concatStyle(style(1), style(2)), "concat(VAL, VAL)") + assert.Equal(t, style(2), concatStyle(nil, style(2)), "concat(NIL, VAL)") +} + +func TestConcatInt(t *testing.T) { + assert.Nil(t, concatInt(nil, nil), "concat(NIL,NIL)") + assert.Equal(t, ptr.Int(1), concatInt(ptr.Int(1), nil), "concat(VAL, NIL)") + assert.Equal(t, ptr.Int(1), concatInt(ptr.Int(1), ptr.Int(2)), "concat(VAL, VAL)") + assert.Equal(t, ptr.Int(2), concatInt(nil, ptr.Int(2)), "concat(NIL, VAL)") +} + +func TestActualInt(t *testing.T) { + assert.Equal(t, ptr.Int(0), actualInt(nil), "actual(NIL)") + assert.Equal(t, ptr.Int(1), actualInt(ptr.Int(1)), "actual(VAL)") +} + +func TestConcatBool(t *testing.T) { + assert.Nil(t, concatBool(nil, nil), "concat(NIL,NIL)") + assert.Equal(t, ptr.Bool(true), concatBool(ptr.Bool(true), nil), "concat(VAL, NIL)") + assert.Equal(t, ptr.Bool(true), concatBool(ptr.Bool(true), ptr.Bool(false)), "concat(VAL, VAL)") + assert.Equal(t, ptr.Bool(false), concatBool(nil, ptr.Bool(false)), "concat(NIL, VAL)") +} + +func TestActualBool(t *testing.T) { + assert.Equal(t, ptr.Bool(false), actualBool(nil), "actual(NIL)") + assert.Equal(t, ptr.Bool(true), actualBool(ptr.Bool(true)), "actual(VAL)") +} diff --git a/config/config.go b/config/config.go index 3c94976..b87321d 100644 --- a/config/config.go +++ b/config/config.go @@ -1,7 +1,5 @@ package config -import "github.com/wacul/ptr" - // Config holds settings for richgo type Config struct { LabelType *LabelType `json:"labelType,omitempty" yaml:"labelType,omitempty"` @@ -20,67 +18,3 @@ type Config struct { Removals []string `json:"removals,omitempty" yaml:"removals,omitempty"` } - -func concatInt(a, b *int) *int { - if a == nil { - if b == nil { - return nil - } - f := *b - return &f - } - return a -} - -func actualInt(b *int) *int { - if b == nil { - return ptr.Int(0) - } - return b -} - -func concatConfig(base, other *Config) *Config { - if base == nil { - if other == nil { - return nil - } - base = &Config{} - } - if other == nil { - other = &Config{} - } - return &Config{ - LabelType: concatLabelType(base.LabelType, other.LabelType), - BuildStyle: concatStyle(base.BuildStyle, other.BuildStyle), - StartStyle: concatStyle(base.StartStyle, other.StartStyle), - PassStyle: concatStyle(base.PassStyle, other.PassStyle), - FailStyle: concatStyle(base.FailStyle, other.FailStyle), - SkipStyle: concatStyle(base.SkipStyle, other.SkipStyle), - FileStyle: concatStyle(base.FileStyle, other.FileStyle), - LineStyle: concatStyle(base.LineStyle, other.LineStyle), - CoverThreshold: concatInt(base.CoverThreshold, other.CoverThreshold), - CoveredStyle: concatStyle(base.CoveredStyle, other.CoveredStyle), - UncoveredStyle: concatStyle(base.UncoveredStyle, other.UncoveredStyle), - Removals: append(base.Removals, other.Removals...), - } -} - -func actualConfig(config *Config) *Config { - if config == nil { - config = &Config{} - } - return &Config{ - LabelType: actualLabelType(config.LabelType), - BuildStyle: actualStyle(config.BuildStyle), - StartStyle: actualStyle(config.StartStyle), - PassStyle: actualStyle(config.PassStyle), - FailStyle: actualStyle(config.FailStyle), - SkipStyle: actualStyle(config.SkipStyle), - FileStyle: actualStyle(config.FileStyle), - LineStyle: actualStyle(config.LineStyle), - CoverThreshold: actualInt(config.CoverThreshold), - CoveredStyle: actualStyle(config.CoveredStyle), - UncoveredStyle: actualStyle(config.UncoveredStyle), - Removals: config.Removals, - } -} diff --git a/config/label_type.go b/config/label_type.go index e77a547..ccd8237 100644 --- a/config/label_type.go +++ b/config/label_type.go @@ -18,17 +18,17 @@ func (l LabelType) String() string { return string(l) } // MarshalJSON implements Marshaler func (l LabelType) MarshalJSON() ([]byte, error) { - return []byte(l.String()), nil + return []byte(fmt.Sprintf("%q", l)), nil } // UnmarshalJSON implements Unmarshaler func (l *LabelType) UnmarshalJSON(raw []byte) error { switch str := string(raw); str { - case "none": + case `"none"`: *l = LabelTypeNone - case "short": + case `"short"`: *l = LabelTypeShort - case "long": + case `"long"`: *l = LabelTypeLong default: return fmt.Errorf("invalid LabelType %s", str) @@ -36,21 +36,11 @@ func (l *LabelType) UnmarshalJSON(raw []byte) error { return nil } -func concatLabelType(base, other *LabelType) *LabelType { - if base == nil { - if other == nil { - return nil - } - return other +// LabelTypes defines the possible values of LabelType +func LabelTypes() []LabelType { + return []LabelType{ + LabelTypeNone, + LabelTypeShort, + LabelTypeLong, } - return base - -} - -func actualLabelType(t *LabelType) *LabelType { - if t == nil { - l := LabelTypeLong - return &l - } - return t } diff --git a/config/label_type_test.go b/config/label_type_test.go new file mode 100644 index 0000000..97ec4e9 --- /dev/null +++ b/config/label_type_test.go @@ -0,0 +1,25 @@ +package config + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLabelTypeMarshaling(t *testing.T) { + type container struct { + L LabelType + } + for _, l := range LabelTypes() { + var d container + raw, err := json.Marshal(container{l}) + if assert.NoError(t, err, "marshaling", l) { + if err := json.Unmarshal(raw, &d); assert.NoError(t, err, "unmarshaling", l) { + assert.Equal(t, l, d.L, "idenpocy of", l) + } + } + } + assert.Error(t, json.Unmarshal([]byte(`{"L":null}`), &container{}), "nil body") + assert.Error(t, json.Unmarshal([]byte(`{"L":""}`), &container{}), "empty body") +} diff --git a/config/load.go b/config/load.go index 3e59ccb..612e330 100644 --- a/config/load.go +++ b/config/load.go @@ -1,8 +1,8 @@ package config import ( - "fmt" "io/ioutil" + "log" "os" "path/filepath" @@ -27,8 +27,6 @@ var ( } // C is global configuration C Config - - loaded Config ) func loadableSources() []string { @@ -57,17 +55,30 @@ func loadableSources() []string { return paths } +var loadForTest func(path string) ([]byte, error) + +func load(path string) ([]byte, error) { + if loadForTest != nil { + return loadForTest(path) + } + return ioutil.ReadFile(path) +} + // Load configurations from file func Load() { paths := loadableSources() c := &defaultConfig for _, p := range paths { - data, err := ioutil.ReadFile(p) + data, err := load(p) if err != nil { + if !os.IsNotExist(err) { + log.Println("error reading from", p, ": ", err) + } continue } + var loaded Config if err := yaml.Unmarshal(data, &loaded); err != nil { - fmt.Println("error unmarshalling yaml from", p, ": ", err) + log.Println("error unmarshaling yaml from", p, ": ", err) continue } c = concatConfig(&loaded, c) @@ -87,8 +98,20 @@ func getEnvPath(envName string) *string { if envPath == "" { return nil } - if stat, err := os.Stat(envPath); err == nil && stat.IsDir() { + if isDir(envPath) { return &envPath } return nil } + +var isDirForTest func(path string) bool + +func isDir(path string) bool { + if isDirForTest != nil { + return isDirForTest(path) + } + if stat, err := os.Stat(path); err == nil && stat.IsDir() { + return true + } + return false +} diff --git a/config/load_test.go b/config/load_test.go new file mode 100644 index 0000000..421b6d4 --- /dev/null +++ b/config/load_test.go @@ -0,0 +1,147 @@ +package config + +import ( + "bytes" + "errors" + "log" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/wacul/ptr" + yaml "gopkg.in/yaml.v2" +) + +func TestLoadableSources(t *testing.T) { + t.Run("in global", func(t *testing.T) { + os.Setenv("HOME", "/") + os.Setenv("GOPATH", "/") + os.Setenv("GOROOT", "/foo/bar/path/not/exists") + os.Setenv("PWD", "/home/kyoh86/go/src/github.com/kyoh86/richgo") + err := os.Setenv(LocalOnlyEnvName, "0") + if err != nil { + t.Errorf("failed to set env: %s", err) + t.FailNow() + } + sources := loadableSources() + if len(sources) == 0 { + t.Errorf("failed to get loadable sources") + } + }) + t.Run("in global with not covered env", func(t *testing.T) { + os.Clearenv() + os.Setenv("HOME", "/home/kyoh86") + os.Setenv("GOPATH", "/home/kyoh86/go") + os.Unsetenv("GOROOT") + os.Setenv("PWD", "/home/kyoh86/go/src/github.com/kyoh86/richgo") + isDirForTest = func(path string) bool { + return len(path) > 0 + } + defer func() { isDirForTest = nil }() + err := os.Setenv(LocalOnlyEnvName, "0") + if err != nil { + t.Errorf("failed to set env: %s", err) + t.FailNow() + } + sources := loadableSources() + if len(sources) == 0 { + t.Errorf("failed to get loadable sources") + } + }) + t.Run("in local", func(t *testing.T) { + err := os.Setenv(LocalOnlyEnvName, "1") + if err != nil { + t.Errorf("failed to set env: %s", err) + t.FailNow() + } + sources := loadableSources() + if len(sources) == 0 { + t.Errorf("failed to get loadable sources") + } + wd, err := os.Getwd() + if err != nil { + t.Errorf("failed to get working directory: %q", err) + t.FailNow() + } + for _, s := range sources { + rel, err := filepath.Rel(wd, s) + if err != nil { + t.Errorf("failed to get relative path to %q from %q: %q", s, wd, err) + } + if strings.HasPrefix(rel, "..") { + t.Errorf("expect that any source will be in local, but not (%q)", s) + } + } + }) +} + +func TestLoad(t *testing.T) { + t.Run("no trick", func(t *testing.T) { + if err := os.Setenv("PWD", "/home/kyoh86/go/src/github.com/kyoh86/richgo"); err != nil { + t.Errorf("failed to set env: %s", err) + t.FailNow() + } + + if err := os.Setenv(LocalOnlyEnvName, "1"); err != nil { + t.Errorf("failed to set env: %s", err) + t.FailNow() + } + Load() + }) + + t.Run("with valid files", func(t *testing.T) { + if err := os.Setenv("PWD", "/home/kyoh86/go/src/github.com/kyoh86/richgo"); err != nil { + t.Errorf("failed to set env: %s", err) + t.FailNow() + } + + if err := os.Setenv(LocalOnlyEnvName, "1"); err != nil { + t.Errorf("failed to set env: %s", err) + t.FailNow() + } + + loadForTest = func(p string) ([]byte, error) { + return yaml.Marshal(&Config{ + CoverThreshold: ptr.Int(10), + }) + } + + Load() + if C.CoverThreshold == nil { + t.Error("expect that a config loaded correctly but 'CoverThreshold' is nil") + t.FailNow() + } + if *C.CoverThreshold != 10 { + t.Errorf("expect that a 'CoverThreshold' is 10, but %d", *C.CoverThreshold) + } + }) + + t.Run("with invalid file", func(t *testing.T) { + loadForTest = func(p string) ([]byte, error) { + return nil, errors.New("test error") + } + + w := bytes.Buffer{} + log.SetFlags(0) + log.SetOutput(&w) + Load() + if !strings.HasPrefix(w.String(), "error reading from") { + t.Error("expect that a Load func puts error in reading a file") + } + }) + + t.Run("with invalid yaml", func(t *testing.T) { + loadForTest = func(p string) ([]byte, error) { + return []byte(`":`), nil + } + + w := bytes.Buffer{} + log.SetFlags(0) + log.SetOutput(&w) + Load() + if !strings.HasPrefix(w.String(), "error unmarshaling yaml from") { + t.Error("expect that a Load func puts error in unmarshaling a file") + } + }) +} diff --git a/config/style.go b/config/style.go index e2b2396..57dfa4e 100644 --- a/config/style.go +++ b/config/style.go @@ -2,7 +2,6 @@ package config import ( "github.com/morikuni/aec" - "github.com/wacul/ptr" ) // Style format the text with ANSI @@ -41,132 +40,38 @@ type Style struct { Background *Color `json:"background,omitempty" yaml:"background,omitempty"` } -func concatStyle(base, other *Style) *Style { - if base == nil { - if other == nil { - return nil - } - base = &Style{} - } - if other == nil { - other = &Style{} - } - return &Style{ - Hide: concatBool(base.Hide, other.Hide), - Bold: concatBool(base.Bold, other.Bold), - Faint: concatBool(base.Faint, other.Faint), - Italic: concatBool(base.Italic, other.Italic), - Underline: concatBool(base.Underline, other.Underline), - BlinkSlow: concatBool(base.BlinkSlow, other.BlinkSlow), - BlinkRapid: concatBool(base.BlinkRapid, other.BlinkRapid), - Inverse: concatBool(base.Inverse, other.Inverse), - Conceal: concatBool(base.Conceal, other.Conceal), - CrossOut: concatBool(base.CrossOut, other.CrossOut), - Frame: concatBool(base.Frame, other.Frame), - Encircle: concatBool(base.Encircle, other.Encircle), - Overline: concatBool(base.Overline, other.Overline), - Foreground: concatColor(base.Foreground, other.Foreground), - Background: concatColor(base.Background, other.Background), - } -} - -func actualStyle(s *Style) *Style { - if s == nil { - s = &Style{} - } - return &Style{ - Hide: actualBool(s.Hide), - Bold: actualBool(s.Bold), - Faint: actualBool(s.Faint), - Italic: actualBool(s.Italic), - Underline: actualBool(s.Underline), - BlinkSlow: actualBool(s.BlinkSlow), - BlinkRapid: actualBool(s.BlinkRapid), - Inverse: actualBool(s.Inverse), - Conceal: actualBool(s.Conceal), - CrossOut: actualBool(s.CrossOut), - Frame: actualBool(s.Frame), - Encircle: actualBool(s.Encircle), - Overline: actualBool(s.Overline), - Foreground: actualColor(s.Foreground), - Background: actualColor(s.Background), - } -} - -func concatBool(a, b *bool) *bool { - if a == nil { - if b == nil { - return nil - } - f := *b - return &f - } - return a -} - -func actualBool(b *bool) *bool { - if b == nil { - return ptr.Bool(false) - } - return b -} - // Apply style To string func (s *Style) Apply(str string) string { - if s == nil { - return str - } - if is(s.Hide) { + if *s.Hide { return "" } - ansi := []aec.ANSI{ - s.Background.B(), - s.Foreground.F(), - } - - if is(s.Bold) { - ansi = append(ansi, aec.Bold) - } - if is(s.Faint) { - ansi = append(ansi, aec.Faint) - } - if is(s.Italic) { - ansi = append(ansi, aec.Italic) - } - if is(s.Underline) { - ansi = append(ansi, aec.Underline) - } - if is(s.BlinkSlow) { - ansi = append(ansi, aec.BlinkSlow) - } - if is(s.BlinkRapid) { - ansi = append(ansi, aec.BlinkRapid) - } - if is(s.Inverse) { - ansi = append(ansi, aec.Inverse) - } - if is(s.Conceal) { - ansi = append(ansi, aec.Conceal) - } - if is(s.CrossOut) { - ansi = append(ansi, aec.CrossOut) - } - if is(s.Frame) { - ansi = append(ansi, aec.Frame) - } - if is(s.Encircle) { - ansi = append(ansi, aec.Encircle) - } - if is(s.Overline) { - ansi = append(ansi, aec.Overline) + ansi := s.Background.B() + ansi = ansi.With(s.Foreground.F()) + for _, style := range []struct { + flag *bool + ansi aec.ANSI + }{ + {s.Bold, aec.Bold}, + {s.Faint, aec.Faint}, + {s.Italic, aec.Italic}, + {s.Underline, aec.Underline}, + {s.BlinkSlow, aec.BlinkSlow}, + {s.BlinkRapid, aec.BlinkRapid}, + {s.Inverse, aec.Inverse}, + {s.Conceal, aec.Conceal}, + {s.CrossOut, aec.CrossOut}, + {s.Frame, aec.Frame}, + {s.Encircle, aec.Encircle}, + {s.Overline, aec.Overline}, + } { + if *style.flag { + ansi = ansi.With(style.ansi) + } } - return aec.Apply(str, ansi...) -} -func is(f *bool) bool { - if f == nil { - return false + if len(ansi.String()) == 0 { + return str } - return *f + return aec.Apply(str, ansi) } diff --git a/config/style_test.go b/config/style_test.go new file mode 100644 index 0000000..e13f3b2 --- /dev/null +++ b/config/style_test.go @@ -0,0 +1,29 @@ +package config + +import ( + "testing" + + "github.com/morikuni/aec" + "github.com/wacul/ptr" +) + +func TestApply(t *testing.T) { + const base = "abc" + for exp, style := range map[string]*Style{ + aec.RedF.Apply(base): actualStyle(&Style{ + Foreground: &Color{Type: ColorTypeName, Name: Red}, + }), + aec.Bold.Apply(base): actualStyle(&Style{ + Bold: ptr.Bool(true), + }), + "": actualStyle(&Style{ + Hide: ptr.Bool(true), + }), + base: actualStyle(nil), + } { + act := style.Apply(base) + if exp != act { + t.Errorf("expect that a style applied as %q, but %q", exp, act) + } + } +} diff --git a/editor/editor.go b/editor/editor.go index e702e15..594fb11 100644 --- a/editor/editor.go +++ b/editor/editor.go @@ -52,8 +52,5 @@ func (s *stream) Write(b []byte) (int, error) { func (s *stream) Close() error { lines := bytes.Split(s.buffer, []byte(`\n`)) s.buffer = nil - if err := s.writeLines(lines); err != nil { - return err - } - return nil + return s.writeLines(lines) } diff --git a/editor/editor_test.go b/editor/editor_test.go new file mode 100644 index 0000000..7d2bc20 --- /dev/null +++ b/editor/editor_test.go @@ -0,0 +1,59 @@ +package editor + +import ( + "bytes" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type editorFunc func(string) (string, error) + +func (f editorFunc) Edit(line string) (string, error) { + return f(line) +} + +func addPrefixEditor(prefix string) Editor { + return editorFunc(func(line string) (string, error) { + return prefix + line, nil + }) +} + +func TestWriter(t *testing.T) { + t.Run("valid editor and writer", func(t *testing.T) { + var buffer bytes.Buffer + stream := Stream(&buffer, addPrefixEditor("A"), addPrefixEditor("B")) + { + _, err := stream.Write([]byte("ab")) + require.NoError(t, err, "write in stream") + assert.Empty(t, buffer.Bytes()) + } + { + _, err := stream.Write([]byte("\ncd")) + require.NoError(t, err, "write in stream") + assert.Equal(t, "BAab\n", buffer.String()) + } + { + require.NoError(t, stream.Close(), "close a stream") + // It's undesirable spec :( + // A following newline is unnecessary. + assert.Equal(t, "BAab\nBAcd\n", buffer.String()) + } + }) + t.Run("invalid editor", func(t *testing.T) { + var buffer bytes.Buffer + stream := Stream(&buffer, editorFunc(func(string) (string, error) { return "", errors.New("test") })) + { + _, err := stream.Write([]byte("ab")) + require.NoError(t, err, "write in stream") + assert.Empty(t, buffer.Bytes()) + } + { + _, err := stream.Write([]byte("\ncd")) + require.EqualError(t, err, "test") + assert.Empty(t, buffer.Bytes()) + } + }) +} diff --git a/editor/parrot_test.go b/editor/parrot_test.go new file mode 100644 index 0000000..366a7b0 --- /dev/null +++ b/editor/parrot_test.go @@ -0,0 +1,16 @@ +package editor + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParrot(t *testing.T) { + const src = "18wRDgGPuvsy6egaevFx" + parrot := Parrot() + out, err := parrot.Edit(src) + if assert.NoError(t, err) { + assert.Equal(t, src, out) + } +} diff --git a/editor/regexp_flow_test.go b/editor/regexp_flow_test.go new file mode 100644 index 0000000..97c7427 --- /dev/null +++ b/editor/regexp_flow_test.go @@ -0,0 +1,35 @@ +package editor + +import ( + "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReplaces(t *testing.T) { + assert.Equal(t, + "A!A!A!-D!E!L!A!X!-C!C!C!", + Replaces( + "aaabbbccc", + RegexRepl{ + Exp: regexp.MustCompile(`b{3}`), + Repl: "-delax-", + }, + RegexRepl{ + Exp: regexp.MustCompile(`\w`), + Func: func(s string) string { + return strings.ToUpper(s) + }, + }, + RegexRepl{ + Exp: regexp.MustCompile(`\w`), + Repl: "x", + Func: func(s string) string { + return s + "!" + }, + }, + ), + ) +} diff --git a/editor/test/label_set_test.go b/editor/test/label_set_test.go new file mode 100644 index 0000000..56e5404 --- /dev/null +++ b/editor/test/label_set_test.go @@ -0,0 +1 @@ +package test diff --git a/editor/tty_test.go b/editor/tty_test.go new file mode 100644 index 0000000..3bc3886 --- /dev/null +++ b/editor/tty_test.go @@ -0,0 +1,14 @@ +package editor + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormattable(t *testing.T) { + if assert.NoError(t, os.Setenv(forceColorFlag, "1")) { + assert.True(t, Formattable(nil)) + } +}