migrate candump log parser to use regex instead
Some checks failed
Go / build (push) Failing after 1m10s

also add more test cases for error logic.
This commit is contained in:
saji 2024-02-28 13:56:57 -06:00
parent 481cac76c6
commit c95593bb86
2 changed files with 71 additions and 48 deletions

View file

@ -30,57 +30,66 @@ func (e *FormatError) Unwrap() error {
return e.err
}
// NewFormatError constructs a new format error.
func NewFormatError(msg string, err error) error {
return &FormatError{msg: msg, err: err}
}
// type LineParserFunc is a function that takes a string
// and returns a can frame. This is useful for common
// can dump formats.
type LineParserFunc func(string) (can.Frame, time.Time, error)
var candumpRegex = regexp.MustCompile(`^\((\d+)\.(\d{6}) \w+ (\w+)#(\w+)$`)
var candumpRegex = regexp.MustCompile(`^\((\d+)\.(\d{6})\) \w+ (\w+)#(\w+)$`)
func parseCanDumpLine(dumpLine string) (frame can.Frame, ts time.Time, err error) {
frame = can.Frame{}
ts = time.Unix(0,0)
// dumpline looks like this:
// (1684538768.521889) can0 200#8D643546
// remove trailing newline
// remove trailing newline/whitespaces
dumpLine = strings.TrimSpace(dumpLine)
segments := strings.Split(dumpLine, " ")
if len(segments) != 3 {
err = NewFormatError("failed to split line", err)
m := candumpRegex.FindStringSubmatch(dumpLine)
if m == nil || len(m) != 5 {
err = NewFormatError("no regex match", nil)
return
}
var unixSeconds, unixMicros int64
fmt.Sscanf(segments[0], "(%d.%d)", &unixSeconds, &unixMicros)
// now we extract the remaining data:
hexes := strings.Split(segments[2], "#") // first portion is id, second is data
unixSeconds, err = strconv.ParseInt(m[1], 10, 0)
if err != nil {
err = NewFormatError("failed to parse unix seconds", err)
return
}
unixMicros, err = strconv.ParseInt(m[2], 10, 0)
if err != nil {
err = NewFormatError("failed to parse unix micros", err)
return
}
id, err := strconv.ParseUint(hexes[0], 16, 64)
id, err := strconv.ParseUint(m[3], 16, 64)
if err != nil {
err = NewFormatError("failed to parse id", err)
return
}
if (len(hexes[1]) % 2) != 0 {
if (len(m[4]) % 2) != 0 {
err = NewFormatError("odd number of hex characters", nil)
return
}
rawData, err := hex.DecodeString(hexes[1])
rawData, err := hex.DecodeString(m[4])
if err != nil {
err = NewFormatError("failed to decode hex data", err)
return
}
// TODO: add extended id support, need an example log and a test.
frame.Id = can.CanID{Id: uint32(id), Extended: false}
frame.Data = rawData
frame.Kind = can.CanDataFrame
ts = time.Unix(unixSeconds, unixMicros)
ts = time.Unix(unixSeconds, unixMicros * int64(time.Microsecond))
return
}
@ -156,10 +165,11 @@ func parseTelemLogLine(line string) (frame can.Frame, ts time.Time, err error) {
}
// this is how we adapt a can frame source into one that produces
// skylab busevents
// BusParserFunc is a function that takes a string and returns a busevent.
type BusParserFunc func(string) (skylab.BusEvent, error)
// parserBusEventMapper takes a line parser (that returns a can frame)
// and makes it return a busEvent instead.
func parserBusEventMapper(f LineParserFunc) BusParserFunc {
return func(s string) (skylab.BusEvent, error) {
var b = skylab.BusEvent{}

View file

@ -19,13 +19,6 @@ func Test_parseCanDumpLine(t *testing.T) {
wantTs time.Time
wantErr bool
}{
{
name: "test garbage",
args: args{dumpLine: "hosireoie"},
wantFrame: can.Frame{},
wantTs: time.Unix(0,0),
wantErr: true,
},
{
name: "test normal data",
args: args{dumpLine: "(1684538768.521889) can0 200#8D643546"},
@ -34,40 +27,16 @@ func Test_parseCanDumpLine(t *testing.T) {
Data: []byte{0x8d, 0x64, 0x35, 0x46},
Kind: can.CanDataFrame,
},
wantTs: time.Unix(1684538768, 521889),
wantTs: time.Unix(1684538768, 521889 * int64(time.Microsecond)),
wantErr: false,
},
{
name: "bad data length",
// odd number of hex data nibbles
args: args{dumpLine: "(1684538768.521889) can0 200#8D64354"},
wantFrame: can.Frame{},
wantTs: time.Unix(0,0),
wantErr: true,
},
{
name: "invalid hex",
// J is not valid hex.
args: args{dumpLine: "(1684538768.521889) can0 200#8D64354J"},
wantFrame: can.Frame{},
wantTs: time.Unix(0,0),
wantErr: true,
},
{
name: "bad time",
// we destroy the time structure.
args: args{dumpLine: "(badtime.521889) can0 200#8D643546"},
wantFrame: can.Frame{},
wantTs: time.Unix(0,0),
wantErr: true,
},
// TODO: add extended id test case
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotFrame, gotTs, err := parseCanDumpLine(tt.args.dumpLine)
if (err != nil) != tt.wantErr {
if (err == nil) == tt.wantErr {
t.Errorf("parseCanDumpLine() error = %v, wantErr %v", err, tt.wantErr)
return
}
@ -80,3 +49,47 @@ func Test_parseCanDumpLine(t *testing.T) {
})
}
}
func Test_parseCanDumpLine_errors(t *testing.T) {
// this test tries a bunch of failure cases to ensure that they are caught and not panicking.
tests := []struct {
name string
input string
}{
{
name: "garbage input",
input: "hoiseorhijkl",
},
{
name: "bad data length",
// odd number of hex data nibbles
input: "(1684538768.521889) can0 200#8D64354",
},
{
name: "invalid hex",
// J is not valid hex.
input: "(1684538768.521889) can0 200#8D64354J",
},
{
name: "bad time",
// we destroy the time structure.
input: "(badtime.521889) can0 200#8D643546",
},
{
name: "utf8 corruption",
// we attempt to mess up the data with broken utf8
input: "(1684538768.521889) can0 200#8D6\xed\xa0\x8043546",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, ts, err := parseCanDumpLine(tt.input)
if err == nil {
t.Fatalf("parseCanDumpLine() expected error but instead got f = %v, ts = %v", f, ts)
}
})
}
}