Skip to content

Commit

Permalink
testar: simpler api, docs
Browse files Browse the repository at this point in the history
  • Loading branch information
sh0rez committed Oct 4, 2024
1 parent 1cd2632 commit 60461b8
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 92 deletions.
67 changes: 0 additions & 67 deletions processor/deltatocumulativeprocessor/internal/testar/archive.go

This file was deleted.

112 changes: 112 additions & 0 deletions processor/deltatocumulativeprocessor/internal/testar/decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// testar is a textual archive (based on [golang.org/x/tools/txtar]) to define
// test fixtures.
//
// Archive data is read into struct fields, optionally calling parsers for field
// types other than string or []byte:
//
// type T struct {
// Literal string `testar:"file1"`
// Parsed int `testar:"file2,myparser"`
// }
//
// var into T
// err := Read(data, &into)
//
// See [Read] and [Parser] for examples.
package testar // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor/internal/testar"

import (
"fmt"
"io/fs"
"reflect"
"strings"

"golang.org/x/tools/txtar"
)

// Read archive data into the fields of struct *T
func Read[T any](data []byte, into *T, parsers ...Format) error {
ar := txtar.Parse(data)
return Decode(ar, into, parsers...)
}

func ReadFile[T any](file string, into *T, parsers ...Format) error {
ar, err := txtar.ParseFile(file)
if err != nil {
return err
}
return Decode(ar, into, parsers...)
}

func Decode[T any](ar *txtar.Archive, into *T, parsers ...Format) error {
arfs, err := txtar.FS(ar)
if err != nil {
return err
}

pv := reflect.ValueOf(into)
if pv.Kind() != reflect.Pointer {
return fmt.Errorf("into must be pointer")
}
sv := pv.Elem()

for i := range sv.NumField() {
f := sv.Type().Field(i)
tag := f.Tag.Get("testar")
if tag == "" {
continue
}

name, format, _ := strings.Cut(tag, ",")
data, err := fs.ReadFile(arfs, name)
if err != nil {
return fmt.Errorf("%s: %w", name, err)
}

err = formats(parsers).Parse(format, data, sv.Field(i).Addr().Interface())
if err != nil {
return fmt.Errorf("%s: %w", name, err)
}
}
return nil
}

type formats []Format

func (fmts formats) Parse(name string, data []byte, into any) error {
if name == "" {
return LiteralParser(data, into)
}

for _, f := range fmts {
if f.name == name {
return f.parse(data, into)
}
}
return fmt.Errorf("no such format: %q", name)
}

type Format struct {
name string
parse func(file []byte, into any) error
}

func Parser(name string, fn func(data []byte, into any) error) Format {
return Format{name: name, parse: fn}
}

// LiteralParser sets data unaltered into a []byte or string
func LiteralParser(data []byte, into any) error {
switch ptr := into.(type) {
case *[]byte:
*ptr = append([]byte(nil), data...)
case *string:
*ptr = string(data)
default:
return fmt.Errorf("pass *[]byte, *string or use a parser. got %T", into)
}
return nil
}
58 changes: 58 additions & 0 deletions processor/deltatocumulativeprocessor/internal/testar/read_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package testar

import (
"fmt"
"strconv"
"strings"
)

func ExampleRead() {
data := []byte(`
-- foo --
hello
-- bar --
world
`)

var into struct {
Foo string `testar:"foo"`
Bar []byte `testar:"bar"`
}

Read(data, &into)
fmt.Printf("foo: %T(%q)\n", into.Foo, into.Foo)
fmt.Printf("bar: %T(%q)\n", into.Bar, into.Bar)

// Output:
// foo: string("hello\n\n")
// bar: []uint8("world\n")
}

func ExampleParser() {
data := []byte(`
-- foobar --
377927
`)

var into struct {
Foobar int `testar:"foobar,atoi"`
}

Read(data, &into, Parser("atoi", func(file []byte, into any) error {
n, err := strconv.Atoi(strings.TrimSpace(string(file)))
if err != nil {
return err
}
*(into.(*int)) = n
return nil
}))

fmt.Printf("foobar: %T(%d)\n", into.Foobar, into.Foobar)

// Output:
// foobar: int(377927)
}
49 changes: 24 additions & 25 deletions processor/deltatocumulativeprocessor/processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ func TestProcessor(t *testing.T) {
continue
}

ar := reader()

type Stage struct {
In pmetric.Metrics `testar:"in,pmetric"`
Out pmetric.Metrics `testar:"out,pmetric"`
}

read := func(file string, into *Stage) error {
return testar.ReadFile(file, into,
testar.Parser("pmetric", UnmarshalMetrics),
)
}

dir := fi.Name()
t.Run(dir, func(t *testing.T) {
file := func(f string) string {
Expand All @@ -55,7 +59,7 @@ func TestProcessor(t *testing.T) {
stages, _ := filepath.Glob(file("*.test"))
for _, file := range stages {
var stage Stage
err := ar.ReadFile(file, &stage)
err := read(file, &stage)
require.NoError(t, err)

sink.Reset()
Expand Down Expand Up @@ -85,28 +89,6 @@ func config(t *testing.T, file string) *self.Config {
return cfg
}

func reader() testar.Reader {
return testar.Reader{
Formats: testar.Formats{
"pmetric": func(data []byte, into any) error {
var tmp any
if err := yaml.Unmarshal(data, &tmp); err != nil {
return err
}
data, err := json.Marshal(tmp)
if err != nil {
return err
}
md, err := (&pmetric.JSONUnmarshaler{}).UnmarshalMetrics(data)
if err != nil {
return err
}
*(into.(*pmetric.Metrics)) = md
return nil
},
},
}
}

func setup(t *testing.T, cfg *self.Config) (processor.Metrics, *consumertest.MetricsSink) {
t.Helper()
Expand All @@ -126,3 +108,20 @@ func setup(t *testing.T, cfg *self.Config) (processor.Metrics, *consumertest.Met

return proc, next
}

func UnmarshalMetrics(data []byte, into any) error {
var tmp any
if err := yaml.Unmarshal(data, &tmp); err != nil {
return err
}
data, err := json.Marshal(tmp)
if err != nil {
return err
}
md, err := (&pmetric.JSONUnmarshaler{}).UnmarshalMetrics(data)
if err != nil {
return err
}
*(into.(*pmetric.Metrics)) = md
return nil
}

0 comments on commit 60461b8

Please sign in to comment.