Skip to content

Commit

Permalink
Backfill test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanmoran committed Mar 1, 2024
1 parent 5617d51 commit f5890ca
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 41 deletions.
6 changes: 3 additions & 3 deletions database.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func NewDatabase(path string) (Database, error) {
line := scanner.Text()
nodes, err := parser.Parse(line)
if err != nil {
panic(err)
return Database{}, fmt.Errorf("failed to parse database file: %w", err)
}

for _, node := range nodes {
Expand Down Expand Up @@ -68,7 +68,7 @@ func NewDatabase(path string) (Database, error) {
}

if err := scanner.Err(); err != nil {
panic(err)
return Database{}, fmt.Errorf("failed to scan database file: %w", err)
}

return database, nil
Expand All @@ -90,7 +90,7 @@ func (d Database) GetNodeAttr(name, attr string) (string, bool) {
func (d Database) Query(query string) ([]Node, error) {
tokens, err := internal.Tokenize(query)
if err != nil {
panic(err)
return nil, err
}

var nodes []Node
Expand Down
47 changes: 33 additions & 14 deletions database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,23 @@ import (
)

func testDatabase(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
path string
)
var Expect = NewWithT(t).Expect

it.Before(func() {
file, err := os.CreateTemp("", "genders")
Expect(err).NotTo(HaveOccurred())
defer file.Close()
context("NewDatabase", func() {
var path string

path = file.Name()
})
it.Before(func() {
file, err := os.CreateTemp("", "genders")
Expect(err).NotTo(HaveOccurred())
defer file.Close()

it.After(func() {
Expect(os.Remove(path)).To(Succeed())
})
path = file.Name()
})

it.After(func() {
Expect(os.Remove(path)).To(Succeed())
})

context("NewDatabase", func() {
it("loads the database from a path on disk", func() {
_, err := libgenders.NewDatabase(path)
Expect(err).NotTo(HaveOccurred())
Expand All @@ -43,6 +42,17 @@ func testDatabase(t *testing.T, context spec.G, it spec.S) {
Expect(err).To(MatchError(ContainSubstring("no-such-file: no such file or directory")))
})
})

context("when the file cannot be parsed", func() {
it.Before(func() {
Expect(os.WriteFile(path, []byte("node[%%-%%] attr=val\n"), 0600)).To(Succeed())
})

it("returns an error", func() {
_, err := libgenders.NewDatabase(path)
Expect(err).To(MatchError(ContainSubstring("failed to parse database file")))
})
})
})
})

Expand Down Expand Up @@ -815,5 +825,14 @@ func testDatabase(t *testing.T, context spec.G, it spec.S) {
})
}
})

context("failure cases", func() {
context("when the query cannot be tokenized", func() {
it("returns an error", func() {
_, err := database.Query(") mismatched parentheses (")
Expect(err).To(MatchError(ContainSubstring("failed to tokenize query")))
})
})
})
})
}
4 changes: 2 additions & 2 deletions internal/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ func TestInternal(t *testing.T) {
suite := spec.New(" libgenders/internal", spec.Report(report.Terminal{}))
suite("Parser", testParser)
suite("Query", testQuery)
suite.Pend("Scanner", testScanner)
suite.Pend("Stack", testStack)
suite("Scanner", testScanner)
suite("Set", testSet)
suite("Stack", testStack)
suite("Token", testToken)
suite.Run(t)
}
39 changes: 27 additions & 12 deletions internal/parser.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package internal

import (
"fmt"
"maps"
"strconv"
"strings"
Expand All @@ -21,7 +22,11 @@ func (p Parser) Parse(line string) ([]Node, error) {
}

fields := strings.Fields(line)
names := p.parseNames(fields[0])
names, err := p.parseNames(fields[0])
if err != nil {
return nil, err
}

var attributes map[string]string
if len(fields) > 1 {
attributes = p.parseAttrs(fields[1])
Expand Down Expand Up @@ -65,7 +70,7 @@ func (p Parser) copyAttrs(attributes map[string]string, name string) map[string]
return attrs
}

func (p Parser) parseNames(field string) []string {
func (p Parser) parseNames(field string) ([]string, error) {
var (
name string
inRange bool
Expand Down Expand Up @@ -97,16 +102,21 @@ func (p Parser) parseNames(field string) []string {

var names []string
for _, f := range fields {
names = append(names, p.parseName(f)...)
fieldNames, err := p.parseName(f)
if err != nil {
return nil, err
}

names = append(names, fieldNames...)
}

return names
return names, nil
}

func (p Parser) parseName(field string) []string {
func (p Parser) parseName(field string) ([]string, error) {
parts := strings.FieldsFunc(field, func(c rune) bool { return c == '[' || c == ']' })
if len(parts) < 2 {
return parts
return parts, nil
}

prefix := parts[0]
Expand All @@ -117,22 +127,27 @@ func (p Parser) parseName(field string) []string {
suffix = parts[2]
}

indices, err := p.parseRange(strings.Split(rng, ",")...)
if err != nil {
return nil, fmt.Errorf("failed to parse name %q: %w", field, err)
}

var names []string
for _, index := range p.parseRange(strings.Split(rng, ",")...) {
for _, index := range indices {
names = append(names, prefix+index+suffix)
}

return names
return names, nil
}

func (p Parser) parseRange(ranges ...string) []string {
func (p Parser) parseRange(ranges ...string) ([]string, error) {
var elems []string
for _, rng := range ranges {
start, end, _ := strings.Cut(rng, "-")

first, err := strconv.Atoi(start)
if err != nil {
panic(err)
return nil, fmt.Errorf("failed to parse range %q: %w", rng, err)
}

if len(end) == 0 {
Expand All @@ -142,13 +157,13 @@ func (p Parser) parseRange(ranges ...string) []string {

last, err := strconv.Atoi(end)
if err != nil {
panic(err)
return nil, fmt.Errorf("failed to parse range %q: %w", rng, err)
}

for i := first; i <= last; i++ {
elems = append(elems, strconv.Itoa(i))
}
}

return elems
return elems, nil
}
16 changes: 16 additions & 0 deletions internal/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ func testParser(t *testing.T, context spec.G, it spec.S) {
}))
})
})

context("failure cases", func() {
context("when the first range value is non-numeric", func() {
it("returns an error", func() {
_, err := parser.Parse("node[banana-25] attr1,attr2=val2")
Expect(err).To(MatchError(ContainSubstring("failed to parse name \"node[banana-25]\": failed to parse range \"banana-25\"")))
})
})

context("when the last range value is non-numeric", func() {
it("returns an error", func() {
_, err := parser.Parse("node[1-banana] attr1,attr2=val2")
Expect(err).To(MatchError(ContainSubstring("failed to parse name \"node[1-banana]\": failed to parse range \"1-banana\"")))
})
})
})
})

context("when the line only specifies a node name", func() {
Expand Down
31 changes: 30 additions & 1 deletion internal/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,38 @@ package internal_test
import (
"testing"

"github.com/ryanmoran/libgenders/internal"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
)

func testScanner(t *testing.T, context spec.G, it spec.S) {
// TODO
var Expect = NewWithT(t).Expect

context("Next", func() {
it("splits a given query into tokenizable characters", func() {
var parts []string
scanner := internal.NewScanner("some query")
for scanner.Len() > 0 {
part, err := scanner.Next()
Expect(err).NotTo(HaveOccurred())
parts = append(parts, part)
}

Expect(parts).To(Equal([]string{"s", "o", "m", "e", " ", "q", "u", "e", "r", "y"}))
})

it("treats set operators specially", func() {
var parts []string
scanner := internal.NewScanner("w && x || y -- z")
for scanner.Len() > 0 {
part, err := scanner.Next()
Expect(err).NotTo(HaveOccurred())
parts = append(parts, part)
}

Expect(parts).To(Equal([]string{"w", " ", "&&", " ", "x", " ", "||", " ", "y", " ", "--", " ", "z"}))
})
})
}
19 changes: 17 additions & 2 deletions internal/stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,24 @@ package internal_test
import (
"testing"

"github.com/ryanmoran/libgenders/internal"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
)

func testStack(t *testing.T, context spec.G, it spec.S) {
// TODO
func testStack(t *testing.T, _ spec.G, it spec.S) {
var Expect = NewWithT(t).Expect

it("acts like a stack for Tokens", func() {
stack := internal.Stack{}
stack.Push(internal.NewToken(internal.ValueTokenKind, "some-value"))
stack.Push(internal.NewToken(internal.ValueTokenKind, "other-value"))
Expect(stack.IsEmpty()).To(BeFalse())

Expect(stack.Top()).To(Equal(internal.NewToken(internal.ValueTokenKind, "other-value")))
Expect(stack.Pop()).To(Equal(internal.NewToken(internal.ValueTokenKind, "other-value")))
Expect(stack.Pop()).To(Equal(internal.NewToken(internal.ValueTokenKind, "some-value")))
Expect(stack.IsEmpty()).To(BeTrue())
})
}
11 changes: 4 additions & 7 deletions internal/token.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package internal

import (
"fmt"
"slices"
"strings"
)
Expand Down Expand Up @@ -40,7 +41,7 @@ func Tokenize(query string) ([]Token, error) {
for scanner.Len() > 0 {
s, err := scanner.Next()
if err != nil {
panic(err)
return nil, err
}

var token Token
Expand Down Expand Up @@ -115,11 +116,7 @@ func Tokenize(query string) ([]Token, error) {
}

if operators.IsEmpty() {
panic("invalid")
}

if operators.Top().Kind != LeftParenTokenKind {
panic("invalid")
return nil, fmt.Errorf("failed to tokenize query %q: mismatched parentheses", query)
}

operators.Pop()
Expand All @@ -132,7 +129,7 @@ func Tokenize(query string) ([]Token, error) {

for !operators.IsEmpty() {
if operators.Top().Kind == LeftParenTokenKind {
panic("invalid")
return nil, fmt.Errorf("failed to tokenize query %q: mismatched parentheses", query)
}

output = append(output, operators.Pop())
Expand Down
16 changes: 16 additions & 0 deletions internal/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,21 @@ func testToken(t *testing.T, context spec.G, it spec.S) {
{Kind: internal.ValueTokenKind, Text: "attr1"},
}))
})

context("failure cases", func() {
context("when the left parentheses in the query are mismatched", func() {
it("returns an error", func() {
_, err := internal.Tokenize("((attr1 && ~attr3) || attr1 -- attr5)) && attr7")
Expect(err).To(MatchError("failed to tokenize query \"((attr1 && ~attr3) || attr1 -- attr5)) && attr7\": mismatched parentheses"))
})
})

context("when the right parentheses in the query are mismatched", func() {
it("returns an error", func() {
_, err := internal.Tokenize("((attr1 && ~attr3) || (attr1 -- attr5) && attr7")
Expect(err).To(MatchError("failed to tokenize query \"((attr1 && ~attr3) || (attr1 -- attr5) && attr7\": mismatched parentheses"))
})
})
})
})
}

0 comments on commit f5890ca

Please sign in to comment.