Improve coverage. Simplify error handling.

This commit is contained in:
Shawn Poulson
2023-08-02 11:41:45 -04:00
parent a9a28759a4
commit 8b6ea5a979
7 changed files with 193 additions and 109 deletions

View File

@@ -4,7 +4,6 @@
package v2 package v2
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -72,8 +71,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem]) pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
in := make(chan buildInput) in := make(chan buildInput)
out := make(chan pathBuildResult) out := make(chan pathBuildResult)
ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{})
defer cancel()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) // input and output goroutines. wg.Add(2) // input and output goroutines.
@@ -104,7 +102,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
currentNode: currentNode, currentNode: currentNode,
pathNode: pathNode, pathNode: pathNode,
}: }:
case <-ctx.Done(): case <-done:
return return
} }
} }
@@ -112,31 +110,25 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
// TranslatePipeline output. // TranslatePipeline output.
go func() { go func() {
defer func() {
cancel()
wg.Done()
}()
for { for {
select { result, ok := <-out
case result, ok := <-out:
if !ok { if !ok {
return break
} }
pathsMap[result.key] = result.value pathsMap[result.key] = result.value
case <-ctx.Done():
return
}
} }
close(done)
wg.Done()
}() }()
translateFunc := func(value buildInput) (retval pathBuildResult, _ error) { translateFunc := func(value buildInput) (pathBuildResult, error) {
pNode := value.pathNode pNode := value.pathNode
cNode := value.currentNode cNode := value.currentNode
path := new(PathItem) path := new(PathItem)
_ = low.BuildModel(pNode, path) _ = low.BuildModel(pNode, path)
err := path.Build(cNode, pNode, idx) err := path.Build(cNode, pNode, idx)
if err != nil { if err != nil {
return retval, err return pathBuildResult{}, err
} }
return pathBuildResult{ return pathBuildResult{
key: low.KeyReference[string]{ key: low.KeyReference[string]{

View File

@@ -4,11 +4,13 @@
package v2 package v2
import ( import (
"fmt"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
func TestPaths_Build(t *testing.T) { func TestPaths_Build(t *testing.T) {
@@ -99,3 +101,28 @@ x-milk: creamy`
assert.Len(t, n.GetExtensions(), 1) assert.Len(t, n.GetExtensions(), 1)
} }
// Test parse failure among many paths.
// This stresses `TranslatePipeline`'s error handling.
func TestPaths_Build_Fail_Many(t *testing.T) {
var yml string
for i := 0; i < 1000; i++ {
format := `"/fresh/code%d":
parameters:
$ref: break
`
yml += fmt.Sprintf(format, i)
}
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
var n Paths
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
assert.Error(t, err)
}

View File

@@ -4,7 +4,6 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -234,16 +233,18 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
return emptyResult, fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column) return emptyResult, fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column)
} }
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
in := make(chan componentInput) in := make(chan componentInput)
out := make(chan componentBuildResult[T]) out := make(chan componentBuildResult[T])
done := make(chan struct{})
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) // input and output goroutines. wg.Add(2) // input and output goroutines.
// Send input. // Send input.
go func() { go func() {
defer wg.Done() defer func() {
close(in)
wg.Done()
}()
var currentLabel *yaml.Node var currentLabel *yaml.Node
for i, node := range nodeValue.Content { for i, node := range nodeValue.Content {
// always ignore extensions // always ignore extensions
@@ -261,11 +262,10 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
node: node, node: node,
currentLabel: currentLabel, currentLabel: currentLabel,
}: }:
case <-ctx.Done(): case <-done:
return return
} }
} }
close(in)
}() }()
// Collect output. // Collect output.
@@ -273,7 +273,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
for result := range out { for result := range out {
componentValues[result.key] = result.value componentValues[result.key] = result.value
} }
cancel() close(done)
wg.Done() wg.Done()
}() }()

View File

@@ -4,11 +4,13 @@
package v3 package v3
import ( import (
"fmt"
"testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"testing"
) )
var testComponentsYaml = ` var testComponentsYaml = `
@@ -164,6 +166,35 @@ func TestComponents_Build_ParameterFail(t *testing.T) {
} }
// Test parse failure among many parameters.
// This stresses `TranslatePipeline`'s error handling.
func TestComponents_Build_ParameterFail_Many(t *testing.T) {
yml := `
parameters:
`
for i := 0; i < 1000; i++ {
format := `
pizza%d:
schema:
$ref: '#/this is a problem.'
`
yml += fmt.Sprintf(format, i)
}
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
var n Components
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestComponents_Build_Fail_TypeFail(t *testing.T) { func TestComponents_Build_Fail_TypeFail(t *testing.T) {
yml := ` yml := `

View File

@@ -4,7 +4,6 @@
package v3 package v3
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"sort" "sort"
@@ -79,8 +78,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem]) pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
in := make(chan buildInput) in := make(chan buildInput)
out := make(chan buildResult) out := make(chan buildResult)
ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{})
defer cancel()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) // input and output goroutines. wg.Add(2) // input and output goroutines.
@@ -111,7 +109,7 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
currentNode: currentNode, currentNode: currentNode,
pathNode: pathNode, pathNode: pathNode,
}: }:
case <-ctx.Done(): case <-done:
return return
} }
} }
@@ -119,21 +117,15 @@ func (p *Paths) Build(_, root *yaml.Node, idx *index.SpecIndex) error {
// TranslatePipeline output. // TranslatePipeline output.
go func() { go func() {
defer func() {
cancel()
wg.Done()
}()
for { for {
select { result, ok := <-out
case result, ok := <-out:
if !ok { if !ok {
return break
} }
pathsMap[result.key] = result.value pathsMap[result.key] = result.value
case <-ctx.Done():
return
}
} }
close(done)
wg.Done()
}() }()
err := datamodel.TranslatePipeline[buildInput, buildResult](in, out, err := datamodel.TranslatePipeline[buildInput, buildResult](in, out,

View File

@@ -4,6 +4,7 @@
package v3 package v3
import ( import (
"fmt"
"testing" "testing"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -478,3 +479,27 @@ x-france: french`
assert.Nil(t, b) assert.Nil(t, b)
} }
// Test parse failure among many paths.
// This stresses `TranslatePipeline`'s error handling.
func TestPaths_Build_Fail_Many(t *testing.T) {
var yml string
for i := 0; i < 1000; i++ {
format := `"/fresh/code%d":
parameters:
$ref: break
`
yml += fmt.Sprintf(format, i)
}
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
idx := index.NewSpecIndex(&idxNode)
var n Paths
err := low.BuildModel(&idxNode, &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
assert.Error(t, err)
}

View File

@@ -1,7 +1,6 @@
package datamodel_test package datamodel_test
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -10,7 +9,6 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time"
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -49,12 +47,29 @@ func TestTranslateSliceParallel(t *testing.T) {
return nil return nil
} }
err := datamodel.TranslateSliceParallel[int, string](sl, translateFunc, resultFunc) err := datamodel.TranslateSliceParallel[int, string](sl, translateFunc, resultFunc)
time.Sleep(10 * time.Millisecond) // DEBUG
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, int64(mapSize), translateCounter) assert.Equal(t, int64(mapSize), translateCounter)
assert.Equal(t, mapSize, resultCounter) assert.Equal(t, mapSize, resultCounter)
}) })
t.Run("nil", func(t *testing.T) {
var sl []int
var translateCounter int64
translateFunc := func(_, value int) (string, error) {
atomic.AddInt64(&translateCounter, 1)
return "", nil
}
var resultCounter int
resultFunc := func(value string) error {
resultCounter++
return nil
}
err := datamodel.TranslateSliceParallel[int, string](sl, translateFunc, resultFunc)
require.NoError(t, err)
assert.Zero(t, translateCounter)
assert.Zero(t, resultCounter)
})
t.Run("Error in translate", func(t *testing.T) { t.Run("Error in translate", func(t *testing.T) {
var sl []int var sl []int
for i := 0; i < mapSize; i++ { for i := 0; i < mapSize; i++ {
@@ -188,6 +203,24 @@ func TestTranslateMapParallel(t *testing.T) {
assert.Equal(t, expectedResults, results) assert.Equal(t, expectedResults, results)
}) })
t.Run("nil", func(t *testing.T) {
var m map[string]int
var translateCounter int64
translateFunc := func(_ string, value int) (string, error) {
atomic.AddInt64(&translateCounter, 1)
return "", nil
}
var resultCounter int
resultFunc := func(value string) error {
resultCounter++
return nil
}
err := datamodel.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
require.NoError(t, err)
assert.Zero(t, translateCounter)
assert.Zero(t, resultCounter)
})
t.Run("Error in translate", func(t *testing.T) { t.Run("Error in translate", func(t *testing.T) {
m := make(map[string]int) m := make(map[string]int)
for i := 0; i < mapSize; i++ { for i := 0; i < mapSize; i++ {
@@ -303,43 +336,39 @@ func TestTranslatePipeline(t *testing.T) {
var inputErr error var inputErr error
in := make(chan int) in := make(chan int)
out := make(chan string) out := make(chan string)
ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{})
defer cancel()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) // input and output goroutines. wg.Add(2) // input and output goroutines.
// Send input. // Send input.
go func() { go func() {
defer wg.Done() defer func() {
close(in)
wg.Done()
}()
for i := 0; i < itemCount; i++ { for i := 0; i < itemCount; i++ {
select { select {
case in <- i: case in <- i:
case <-ctx.Done(): case <-done:
inputErr = errors.New("Context canceled unexpectedly") inputErr = errors.New("Exited unexpectedly")
return
} }
} }
close(in)
}() }()
// Collect output. // Collect output.
var resultCounter int var resultCounter int
go func() { go func() {
defer func() {
cancel()
wg.Done()
}()
for { for {
select { result, ok := <-out
case result, ok := <-out:
if !ok { if !ok {
return break
} }
assert.Equal(t, strconv.Itoa(resultCounter), result) assert.Equal(t, strconv.Itoa(resultCounter), result)
resultCounter++ resultCounter++
case <-ctx.Done():
return
}
} }
close(done)
wg.Done()
}() }()
err := datamodel.TranslatePipeline[int, string](in, out, err := datamodel.TranslatePipeline[int, string](in, out,
@@ -356,41 +385,36 @@ func TestTranslatePipeline(t *testing.T) {
t.Run("Error in translate", func(t *testing.T) { t.Run("Error in translate", func(t *testing.T) {
in := make(chan int) in := make(chan int)
out := make(chan string) out := make(chan string)
ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{})
defer cancel()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) // input and output goroutines. wg.Add(2) // input and output goroutines.
// Send input. // Send input.
go func() { go func() {
defer wg.Done()
for i := 0; i < itemCount; i++ { for i := 0; i < itemCount; i++ {
select { select {
case in <- i: case in <- i:
case <-ctx.Done(): case <-done:
// Context expected to cancel after the first translate. // Expected to exit after the first translate.
} }
} }
close(in) close(in)
wg.Done()
}() }()
// Collect output. // Collect output.
var resultCounter int var resultCounter int
go func() { go func() {
defer func() { defer func() {
cancel() close(done)
wg.Done() wg.Done()
}() }()
for { for {
select { _, ok := <-out
case _, ok := <-out:
if !ok { if !ok {
return return
} }
resultCounter++ resultCounter++
case <-ctx.Done():
return
}
} }
}() }()
@@ -408,8 +432,7 @@ func TestTranslatePipeline(t *testing.T) {
var inputErr error var inputErr error
in := make(chan int) in := make(chan int)
out := make(chan string) out := make(chan string)
ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{})
defer cancel()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) // input and output goroutines. wg.Add(2) // input and output goroutines.
@@ -419,8 +442,8 @@ func TestTranslatePipeline(t *testing.T) {
for i := 0; i < itemCount; i++ { for i := 0; i < itemCount; i++ {
select { select {
case in <- i: case in <- i:
case <-ctx.Done(): case <-done:
inputErr = errors.New("Context canceled unexpectedly") inputErr = errors.New("Exited unexpectedly")
} }
} }
close(in) close(in)
@@ -429,21 +452,15 @@ func TestTranslatePipeline(t *testing.T) {
// Collect output. // Collect output.
var resultCounter int var resultCounter int
go func() { go func() {
defer func() {
cancel()
wg.Done()
}()
for { for {
select { _, ok := <-out
case _, ok := <-out:
if !ok { if !ok {
return break
} }
resultCounter++ resultCounter++
case <-ctx.Done():
return
}
} }
close(done)
wg.Done()
}() }()
err := datamodel.TranslatePipeline[int, string](in, out, err := datamodel.TranslatePipeline[int, string](in, out,