Compare commits
10 Commits
v0.1.0-rc0
...
main
Author | SHA1 | Date |
---|---|---|
William Perron | aea9ae7ce4 | 10 months ago |
William Perron | 2fd259c162 | 1 year ago |
William Perron | 5db0804f1d | 1 year ago |
William Perron | e99d5309e7 | 1 year ago |
William Perron | d4d9394a1f | 1 year ago |
William Perron | 2db3183288 | 1 year ago |
William Perron | ed61be7775 | 1 year ago |
William Perron | 2bb7fc611d | 1 year ago |
William Perron | a06f28f122 | 1 year ago |
William Perron | 83e720e5a2 | 1 year ago |
@ -0,0 +1,2 @@
|
||||
bin/
|
||||
.vscode/
|
@ -0,0 +1,14 @@
|
||||
all: bindir
|
||||
go build -o ./bin ./cmd/...
|
||||
|
||||
httpcat: bindir
|
||||
go build -o ./bin ./cmd/httpcat/...
|
||||
|
||||
mdfmt: bindir
|
||||
go build -o ./bin ./cmd/md-fmt/...
|
||||
|
||||
otelq: bindir
|
||||
go build -o ./bin ./cmd/otelq/...
|
||||
|
||||
bindir:
|
||||
mkdir ./bin; exit 0
|
@ -0,0 +1,5 @@
|
||||
# HTTP Cat
|
||||
|
||||
An HTTP server that returns the HTTP request as text in the response body.
|
||||
Useful for debugging proxy configurations.
|
||||
|
@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ports = flag.String("ports", "", "comma-separated list of ports to listen on")
|
||||
)
|
||||
|
||||
type CatHandler struct{}
|
||||
|
||||
var _ http.Handler = &CatHandler{}
|
||||
|
||||
func (cs *CatHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
log.Printf("[%s] %s %s", req.Host, req.Method, req.URL.Path)
|
||||
|
||||
w.Write([]byte(fmt.Sprintf("%s %s %s\n", req.Proto, req.Method, req.URL.Path)))
|
||||
for k, v := range req.Header {
|
||||
w.Write([]byte(fmt.Sprintf("%s: %s\n", k, strings.Join(v, "; "))))
|
||||
}
|
||||
w.Write([]byte{'\n'})
|
||||
|
||||
if req.Body != nil {
|
||||
io.Copy(w, req.Body)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
errch := make(chan error)
|
||||
done := make(chan struct{})
|
||||
for _, p := range strings.Split(*ports, ",") {
|
||||
port := strings.TrimSpace(p)
|
||||
if port[0] != ':' {
|
||||
port = ":" + port
|
||||
}
|
||||
go func() {
|
||||
log.Printf("listening on %s\n", port)
|
||||
if err := http.ListenAndServe(port, new(CatHandler)); err != nil {
|
||||
log.Println(err)
|
||||
errch <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-errch
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
<-done
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
# CSV to Markdown Table Formatter
|
||||
|
||||
Takes a csv-like file as input and outputs a formatted markdown table with it.
|
||||
|
||||
## Usage
|
||||
|
||||
Run `md-fmt -help` for the list of command line arguments. For tab-separated
|
||||
files, in order for the separator to be passed correcly from the command line to
|
||||
the program, use `-sep=$'\t'` to correctly escape the character.
|
@ -0,0 +1,175 @@
|
||||
// md-fmt takes a csv or tsv input file and outputs a formatted markdown table
|
||||
// with the data.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/csv"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
sourcePath = flag.String("source", "", "path to the input file")
|
||||
separator = flag.String("sep", ",", "separator character to use when reading the csv file")
|
||||
lazyQuotes = flag.Bool("lazy-quotes", false, "controls the lazy-quotes setting on the csv reader")
|
||||
toCSV = flag.Bool("to-csv", false, "parses markdown table and encodes it to csv (opposite operation)")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
fd, err := os.Open(*sourcePath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to open source file %s: %s\n", *sourcePath, err)
|
||||
}
|
||||
|
||||
if *toCSV {
|
||||
markdownToCSV(fd, os.Stdout)
|
||||
} else {
|
||||
csvToMarkdown(fd, os.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func csvToMarkdown(r io.ReadSeeker, w io.Writer) {
|
||||
sep := []rune(*separator)[0]
|
||||
read := csv.NewReader(r)
|
||||
read.Comma = sep
|
||||
read.TrimLeadingSpace = true
|
||||
read.LazyQuotes = *lazyQuotes
|
||||
|
||||
rec, err := read.Read()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error reading from csv file: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
widths := make([]int, len(rec))
|
||||
for i, col := range rec {
|
||||
widths[i] = max(widths[i], len(col))
|
||||
}
|
||||
for {
|
||||
rec, err := read.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error reading from csv file: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for i, col := range rec {
|
||||
widths[i] = max(widths[i], len(col))
|
||||
}
|
||||
}
|
||||
|
||||
c := make([]string, len(widths))
|
||||
for i := 0; i < len(c); i++ {
|
||||
c[i] = " %-*s "
|
||||
}
|
||||
pattern := fmt.Sprintf("|%s|\n", strings.Join(c, "|"))
|
||||
|
||||
// Reset file descriptor cursor and take new CSV reader from it
|
||||
r.Seek(0, 0)
|
||||
read = csv.NewReader(r)
|
||||
read.Comma = sep
|
||||
read.TrimLeadingSpace = true
|
||||
read.LazyQuotes = *lazyQuotes
|
||||
|
||||
// Format header row
|
||||
rec, err = read.Read()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to read next csv record: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
curr := make([]any, 0, 2*len(widths))
|
||||
for i := range widths {
|
||||
curr = append(curr, widths[i], rec[i])
|
||||
}
|
||||
fmt.Fprintf(w, pattern, curr...)
|
||||
|
||||
// Format header separator row
|
||||
curr = curr[:0] // empty slice but preserve capacity
|
||||
for i := range widths {
|
||||
curr = append(curr, widths[i], strings.Repeat("-", widths[i]))
|
||||
}
|
||||
fmt.Fprintf(w, pattern, curr...)
|
||||
|
||||
for {
|
||||
rec, err := read.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error reading from csv file: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
curr = curr[:0]
|
||||
for i := range widths {
|
||||
curr = append(curr, widths[i], rec[i])
|
||||
}
|
||||
fmt.Fprintf(w, pattern, curr...)
|
||||
}
|
||||
}
|
||||
|
||||
func markdownToCSV(r io.ReadSeeker, w io.Writer) {
|
||||
cw := csv.NewWriter(w)
|
||||
defer func() {
|
||||
cw.Flush()
|
||||
if err := cw.Error(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error occured during scan: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// for now we're assuming there's only one table in a markdown file, this
|
||||
// flag keeps track of whether or not we've seen a table already, and is
|
||||
// used to exit the loop early once we're done reading the table, ignoring
|
||||
// subsequent lines in the file.
|
||||
var seen bool
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "reading standard input:", err)
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if !(strings.HasPrefix(line, "|") && strings.HasSuffix(line, "|")) {
|
||||
if seen {
|
||||
break // exit early if we've seen a table already
|
||||
}
|
||||
continue // keep going until we see a table
|
||||
}
|
||||
|
||||
// the rest of the body only applies to lines that are part of a table
|
||||
// i.e. that start and end with a pipe character (`|`)
|
||||
seen = true
|
||||
|
||||
line = strings.Trim(line, "|")
|
||||
parts := strings.Split(line, "|")
|
||||
for i := 0; i < len(parts); i++ {
|
||||
parts[i] = strings.TrimSpace(parts[i])
|
||||
}
|
||||
|
||||
// ignore separator line
|
||||
if parts[0] == strings.Repeat("-", len(parts[0])) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cw.Write(parts); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to write to output: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
# Tempo To OTLP
|
||||
|
||||
Converts a Tempo protobuf message to an OTLP protobuf message
|
@ -0,0 +1,181 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/golang/protobuf/proto" // NOTE: keep this, it's required to unmarshall the tempopb
|
||||
"github.com/grafana/tempo/pkg/tempopb"
|
||||
tempocommonv1 "github.com/grafana/tempo/pkg/tempopb/common/v1"
|
||||
tempotracev1 "github.com/grafana/tempo/pkg/tempopb/trace/v1"
|
||||
commonv1 "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
resourcev1 "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
tracev1 "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
newproto "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
file = flag.String("file", "", "Tempo proto file. if set, will not use stdin.")
|
||||
out = flag.String("out", "", "Output file destination. Defaults to stdout.")
|
||||
// asJSON = flag.Bool("json", false, "Read input as jsonpb.") // TODO(wperron) implement this
|
||||
)
|
||||
|
||||
// TODO(wperron) implement reading from stdin
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var output io.Writer
|
||||
if *out == "" {
|
||||
output = os.Stdout
|
||||
} else {
|
||||
output = must(os.OpenFile(*out, os.O_CREATE+os.O_RDWR, 0o664))
|
||||
}
|
||||
|
||||
h := must(os.Open(*file))
|
||||
bs := must(io.ReadAll(h))
|
||||
var trace tempopb.Trace
|
||||
if err := proto.Unmarshal(bs, &trace); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
td := must(convert(trace))
|
||||
bs = must(newproto.Marshal(&td))
|
||||
written := must(io.Copy(output, bytes.NewReader(bs)))
|
||||
|
||||
fmt.Fprintf(os.Stderr, "completed successfully, %d bytes written", written)
|
||||
}
|
||||
|
||||
func convert(trace tempopb.Trace) (tracev1.TracesData, error) {
|
||||
td := make([]*tracev1.ResourceSpans, 0, len(trace.Batches))
|
||||
for _, resourceSpans := range trace.Batches {
|
||||
rs := &tracev1.ResourceSpans{
|
||||
Resource: &resourcev1.Resource{
|
||||
Attributes: convertAttributes(resourceSpans.Resource.Attributes),
|
||||
DroppedAttributesCount: resourceSpans.Resource.DroppedAttributesCount,
|
||||
},
|
||||
ScopeSpans: make([]*tracev1.ScopeSpans, 0),
|
||||
}
|
||||
for _, ils := range resourceSpans.InstrumentationLibrarySpans {
|
||||
scope := &tracev1.ScopeSpans{
|
||||
Spans: make([]*tracev1.Span, 0),
|
||||
}
|
||||
|
||||
scope.Scope = &commonv1.InstrumentationScope{
|
||||
Name: ils.InstrumentationLibrary.Name,
|
||||
Version: ils.InstrumentationLibrary.Version,
|
||||
Attributes: convertAttributes(resourceSpans.Resource.Attributes), // TODO(wperron) is this right?
|
||||
DroppedAttributesCount: resourceSpans.Resource.DroppedAttributesCount, // TODO(wperron) is this right?
|
||||
}
|
||||
|
||||
for _, span := range ils.Spans {
|
||||
otelSpan := &tracev1.Span{
|
||||
TraceId: span.TraceId,
|
||||
SpanId: span.SpanId,
|
||||
TraceState: span.TraceState,
|
||||
ParentSpanId: span.ParentSpanId,
|
||||
Name: span.Name,
|
||||
Kind: tracev1.Span_SpanKind(span.Kind),
|
||||
StartTimeUnixNano: span.StartTimeUnixNano,
|
||||
EndTimeUnixNano: span.EndTimeUnixNano,
|
||||
Attributes: convertAttributes(span.Attributes),
|
||||
DroppedAttributesCount: span.DroppedAttributesCount,
|
||||
Events: convertEvents(span.Events),
|
||||
DroppedEventsCount: span.DroppedEventsCount,
|
||||
Links: convertLinks(span.Links),
|
||||
DroppedLinksCount: span.DroppedLinksCount,
|
||||
Status: convertStatus(span.Status),
|
||||
}
|
||||
scope.Spans = append(scope.Spans, otelSpan)
|
||||
}
|
||||
|
||||
rs.ScopeSpans = append(rs.ScopeSpans, scope)
|
||||
}
|
||||
td = append(td, rs)
|
||||
}
|
||||
|
||||
return tracev1.TracesData{
|
||||
ResourceSpans: td,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertAttributes(attrs []*tempocommonv1.KeyValue) []*commonv1.KeyValue {
|
||||
kvs := make([]*commonv1.KeyValue, 0, len(attrs))
|
||||
for _, a := range attrs {
|
||||
kvs = append(kvs, &commonv1.KeyValue{
|
||||
Key: a.Key,
|
||||
Value: convertAnyValue(a.Value),
|
||||
})
|
||||
}
|
||||
return kvs
|
||||
}
|
||||
|
||||
func convertAnyValue(av *tempocommonv1.AnyValue) *commonv1.AnyValue {
|
||||
inner := av.GetValue()
|
||||
v := &commonv1.AnyValue{}
|
||||
switch inner.(type) {
|
||||
case *tempocommonv1.AnyValue_StringValue:
|
||||
v.Value = &commonv1.AnyValue_StringValue{StringValue: av.GetStringValue()}
|
||||
case *tempocommonv1.AnyValue_IntValue:
|
||||
v.Value = &commonv1.AnyValue_IntValue{IntValue: av.GetIntValue()}
|
||||
case *tempocommonv1.AnyValue_DoubleValue:
|
||||
v.Value = &commonv1.AnyValue_DoubleValue{DoubleValue: av.GetDoubleValue()}
|
||||
case *tempocommonv1.AnyValue_BoolValue:
|
||||
v.Value = &commonv1.AnyValue_BoolValue{BoolValue: av.GetBoolValue()}
|
||||
case *tempocommonv1.AnyValue_ArrayValue:
|
||||
inner := av.GetArrayValue().Values
|
||||
i := make([]*commonv1.AnyValue, 0, len(inner))
|
||||
for _, val := range inner {
|
||||
i = append(i, convertAnyValue(val))
|
||||
}
|
||||
v.Value = &commonv1.AnyValue_ArrayValue{ArrayValue: &commonv1.ArrayValue{Values: i}}
|
||||
case *tempocommonv1.AnyValue_KvlistValue:
|
||||
inner := av.GetKvlistValue().Values
|
||||
v.Value = &commonv1.AnyValue_KvlistValue{KvlistValue: &commonv1.KeyValueList{Values: convertAttributes(inner)}}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func convertEvents(events []*tempotracev1.Span_Event) []*tracev1.Span_Event {
|
||||
ev := make([]*tracev1.Span_Event, 0, len(events))
|
||||
for _, event := range events {
|
||||
ev = append(ev, &tracev1.Span_Event{
|
||||
TimeUnixNano: event.TimeUnixNano,
|
||||
Name: event.Name,
|
||||
DroppedAttributesCount: event.DroppedAttributesCount,
|
||||
Attributes: convertAttributes(event.Attributes),
|
||||
})
|
||||
}
|
||||
return ev
|
||||
}
|
||||
|
||||
func convertLinks(links []*tempotracev1.Span_Link) []*tracev1.Span_Link {
|
||||
ls := make([]*tracev1.Span_Link, 0, len(links))
|
||||
for _, link := range links {
|
||||
ls = append(ls, &tracev1.Span_Link{
|
||||
TraceId: link.TraceId,
|
||||
SpanId: link.SpanId,
|
||||
TraceState: link.TraceState,
|
||||
DroppedAttributesCount: link.DroppedAttributesCount,
|
||||
Attributes: convertAttributes(link.Attributes),
|
||||
})
|
||||
}
|
||||
return ls
|
||||
}
|
||||
|
||||
func convertStatus(status *tempotracev1.Status) *tracev1.Status {
|
||||
return &tracev1.Status{
|
||||
Message: status.Message,
|
||||
Code: tracev1.Status_StatusCode(status.Code),
|
||||
}
|
||||
}
|
||||
|
||||
func must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package gziputil
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Gzip struct {
|
||||
magic []byte
|
||||
cm byte
|
||||
flg byte
|
||||
mtime uint32
|
||||
xfl byte
|
||||
os byte
|
||||
xlen uint16
|
||||
xflds []byte
|
||||
crc32 uint32
|
||||
isize uint32
|
||||
|
||||
rest []byte
|
||||
trailer []byte
|
||||
}
|
||||
|
||||
func explainGzip(bs []byte) Gzip {
|
||||
g := Gzip{
|
||||
magic: bs[0:2],
|
||||
cm: bs[2],
|
||||
flg: bs[3],
|
||||
mtime: binary.BigEndian.Uint32(bs[4:8]),
|
||||
xfl: bs[8],
|
||||
os: bs[9],
|
||||
}
|
||||
|
||||
offset := 10
|
||||
if g.xfl&2 == 1 {
|
||||
g.xlen = binary.BigEndian.Uint16(bs[offset : offset+2])
|
||||
offset += 2
|
||||
|
||||
g.xflds = bs[offset : offset+int(g.xlen)]
|
||||
offset += int(g.xlen)
|
||||
}
|
||||
|
||||
g.crc32 = binary.BigEndian.Uint32(bs[offset : offset+4])
|
||||
offset += 4
|
||||
|
||||
g.isize = binary.BigEndian.Uint32(bs[offset : offset+4])
|
||||
offset += 4
|
||||
|
||||
g.rest = bs[offset : len(bs)-8]
|
||||
offset = len(bs) - 8
|
||||
|
||||
g.trailer = bs[offset:]
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func (g Gzip) String() string {
|
||||
return fmt.Sprintf("magic=%04x cm=%02x flg=%02x mtime=%d xfl=%02x os=%02x xlen=%d crc32=%d isize=%d rest=%x trailer=%08x", g.magic, g.cm, g.flg, g.mtime, g.xfl, g.os, g.xlen, g.crc32, g.isize, g.rest, g.trailer)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
# Integration Test Util
|
||||
|
||||
Based off of Deno's internal integration test utilities. This is meant to test
|
||||
external systems that are hard to mock or can't rely fully on dependency
|
||||
injection.
|
@ -0,0 +1,59 @@
|
||||
package integrationtest
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const WILDCARD = "[WILDCARD]"
|
||||
|
||||
func AssertMatchesText(t *testing.T, expected, actual string) {
|
||||
if !strings.Contains(expected, WILDCARD) {
|
||||
assert.Equal(t, expected, actual)
|
||||
} else if !wildcardMatch(expected, actual) {
|
||||
t.Errorf("texts do not match. expected \"%s\", got \"%s\"", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func wildcardMatch(expected, actual string) bool {
|
||||
return patternMatch(expected, actual, WILDCARD)
|
||||
}
|
||||
|
||||
func patternMatch(expected, actual, pattern string) bool {
|
||||
if expected == pattern {
|
||||
return true
|
||||
}
|
||||
|
||||
parts := strings.Split(expected, pattern)
|
||||
if len(parts) == 1 {
|
||||
return expected == actual
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(actual, parts[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
if lines := strings.Split(expected, "\n"); len(lines) > 0 && lines[0] == pattern {
|
||||
actual = "\n" + actual
|
||||
}
|
||||
|
||||
t := []string{actual[:len(parts[0])], actual[len(parts[0]):]}
|
||||
for i, part := range parts {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if i == len(parts)-1 && (part == "" || part == "\n") {
|
||||
return true
|
||||
}
|
||||
|
||||
if found := strings.Index(t[1], part); found != -1 {
|
||||
t = []string{t[1][:found+len(part)], t[1][found+len(part):]}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return t[1] == ""
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package integrationtest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_patternMatch(t *testing.T) {
|
||||
type args struct {
|
||||
expected string
|
||||
actual string
|
||||
pattern string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "foobarbaz matches",
|
||||
args: args{
|
||||
expected: "foo[BAR]baz",
|
||||
actual: "foobarbaz",
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "foobarbaz does not match",
|
||||
args: args{
|
||||
expected: "foo[BAR]baz",
|
||||
actual: "foobazbar",
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "wildcard matches but not rest of string",
|
||||
args: args{
|
||||
expected: "foo[BAR]baz",
|
||||
actual: "foobarfizz",
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "just wildcard",
|
||||
args: args{
|
||||
expected: "[BAR]",
|
||||
actual: "foobarbaz",
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "prefix",
|
||||
args: args{
|
||||
expected: "prefix[BAR]",
|
||||
actual: "prefixbarbaz",
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "prefix does not match",
|
||||
args: args{
|
||||
expected: "prefix[BAR]",
|
||||
actual: "somethingelsebarbaz",
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "suffix",
|
||||
args: args{
|
||||
expected: "[BAR]suffix",
|
||||
actual: "foobarbazsuffix",
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no pattern",
|
||||
args: args{
|
||||
expected: "exact match",
|
||||
actual: "exact match",
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiline",
|
||||
args: args{
|
||||
expected: `first line
|
||||
[BAR]
|
||||
last line`,
|
||||
actual: `first line
|
||||
second line
|
||||
last line`,
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiline prefix",
|
||||
args: args{
|
||||
expected: `[BAR]
|
||||
first line
|
||||
last line`,
|
||||
actual: `[BAR]
|
||||
first line
|
||||
last line`,
|
||||
pattern: "[BAR]",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := patternMatch(tt.args.expected, tt.args.actual, tt.args.pattern); got != tt.want {
|
||||
t.Errorf("patternMatch() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in new issue