This is an automated email from the ASF dual-hosted git repository. sjanc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-newtmgr.git
commit 48a992209d0ade35b856bca12e6205ffbb364bad Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 00:25:31 2026 +0200 nmxact/nmxutil: Add unit tests for pure helpers Cover Fragment for positive MTUs, NMP sequence and CoAP token wrap-around, SeqToToken, GetNextId, the CBOR encode/decode helpers (including invalid input), StopAndDrainTimer and the nmxerr error types with their Is*/To* helpers. Assisted-by: Claude:claude-opus-5-5 --- nmxact/nmxutil/nmxerr_test.go | 134 +++++++++++++++++++++++++++++ nmxact/nmxutil/nmxutil_test.go | 185 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+) diff --git a/nmxact/nmxutil/nmxerr_test.go b/nmxact/nmxutil/nmxerr_test.go new file mode 100644 index 0000000..c47d539 --- /dev/null +++ b/nmxact/nmxutil/nmxerr_test.go @@ -0,0 +1,134 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package nmxutil + +import ( + "errors" + "testing" +) + +func TestErrorPredicates(t *testing.T) { + type pred struct { + name string + fn func(error) bool + } + + preds := []pred{ + {"IsRspTimeout", IsRspTimeout}, + {"IsBleSesnDisconnect", IsBleSesnDisconnect}, + {"IsSesnAlreadyOpen", IsSesnAlreadyOpen}, + {"IsSesnClosed", IsSesnClosed}, + {"IsScanTmo", IsScanTmo}, + {"IsXport", IsXport}, + {"IsBleHost", IsBleHost}, + {"IsAlready", IsAlready}, + {"IsBleSecurity", IsBleSecurity}, + } + + // errs[i] must satisfy exactly preds[i]. + errs := []error{ + FmtRspTimeoutError("tmo %d", 1), + NewBleSesnDisconnectError(0x13, "disc"), + NewSesnAlreadyOpenError("open"), + NewSesnClosedError("closed"), + NewScanTmoError("scan"), + NewXportError("xport"), + FmtBleHostError(5, "host %s", "err"), + NewAlreadyError("already"), + NewBleSecurityError("sec"), + } + + for i, err := range errs { + for j, p := range preds { + want := i == j + if got := p.fn(err); got != want { + t.Errorf("%s(%T) = %v, want %v", p.name, err, got, want) + } + } + } + + for _, p := range preds { + if p.fn(nil) { + t.Errorf("%s(nil) = true", p.name) + } + if p.fn(errors.New("plain")) { + t.Errorf("%s(plain error) = true", p.name) + } + } +} + +func TestErrorText(t *testing.T) { + tests := []struct { + err error + want string + }{ + {FmtRspTimeoutError("a=%d b=%s", 1, "x"), "a=1 b=x"}, + {NewBleSesnDisconnectError(1, "disc"), "disc"}, + {NewSesnAlreadyOpenError("open"), "open"}, + {NewSesnClosedError("closed"), "closed"}, + {NewScanTmoError("scan"), "scan"}, + {NewXportError("xport"), "xport"}, + {FmtBleHostError(1, "%s-%d", "h", 2), "h-2"}, + {NewAlreadyError("already"), "already"}, + {NewBleSecurityError("sec"), "sec"}, + } + + for _, tt := range tests { + if got := tt.err.Error(); got != tt.want { + t.Errorf("%T.Error() = %q, want %q", tt.err, got, tt.want) + } + } +} + +func TestToBleHost(t *testing.T) { + e := NewBleHostError(0x205, "x") + if got := ToBleHost(e); got != e { + t.Fatalf("ToBleHost() = %v, want %v", got, e) + } + if got := ToBleHost(e); got.Status != 0x205 { + t.Fatalf("Status = %#x, want 0x205", got.Status) + } + if got := ToBleHost(NewXportError("x")); got != nil { + t.Fatalf("ToBleHost(xport) = %v, want nil", got) + } + if got := ToBleHost(nil); got != nil { + t.Fatalf("ToBleHost(nil) = %v, want nil", got) + } +} + +func TestToBleSecurity(t *testing.T) { + e := NewBleSecurityError("x") + if got := ToBleSecurity(e); got != e { + t.Fatalf("ToBleSecurity() = %v, want %v", got, e) + } + if got := ToBleSecurity(NewBleHostError(1, "x")); got != nil { + t.Fatalf("ToBleSecurity(host) = %v, want nil", got) + } + if got := ToBleSecurity(nil); got != nil { + t.Fatalf("ToBleSecurity(nil) = %v, want nil", got) + } +} + +func TestBleSesnDisconnectReason(t *testing.T) { + e := NewBleSesnDisconnectError(0x13, "disc") + if e.Reason != 0x13 { + t.Fatalf("Reason = %#x, want 0x13", e.Reason) + } +} diff --git a/nmxact/nmxutil/nmxutil_test.go b/nmxact/nmxutil/nmxutil_test.go new file mode 100644 index 0000000..13967cd --- /dev/null +++ b/nmxact/nmxutil/nmxutil_test.go @@ -0,0 +1,185 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package nmxutil + +import ( + "bytes" + "reflect" + "testing" + "time" +) + +func TestFragment(t *testing.T) { + data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + + tests := []struct { + name string + b []byte + mtu int + want [][]byte + }{ + {"empty", []byte{}, 4, [][]byte{}}, + {"nil", nil, 4, [][]byte{}}, + {"mtu 1", data[:3], 1, [][]byte{{0}, {1}, {2}}}, + {"uneven", data, 4, + [][]byte{{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9}}}, + {"even", data, 5, + [][]byte{{0, 1, 2, 3, 4}, {5, 6, 7, 8, 9}}}, + {"exact", data, 10, [][]byte{data}}, + {"larger mtu", data, 100, [][]byte{data}}, + } + + for _, tt := range tests { + got := Fragment(tt.b, tt.mtu) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%s: Fragment() = %v, want %v", tt.name, got, tt.want) + } + + // Fragments always reassemble into the original buffer. + if j := bytes.Join(got, nil); !bytes.Equal(j, tt.b) { + t.Errorf("%s: joined fragments = %v, want %v", tt.name, j, tt.b) + } + } +} + +func TestNextNmpSeqWrap(t *testing.T) { + seqMutex.Lock() + nextNmpSeq = 0xfe + nmpSeqBeenRead = true + seqMutex.Unlock() + + for _, want := range []uint8{0xfe, 0xff, 0x00, 0x01} { + if got := NextNmpSeq(); got != want { + t.Fatalf("NextNmpSeq() = %#x, want %#x", got, want) + } + } +} + +func TestNextTokenWrap(t *testing.T) { + seqMutex.Lock() + nextOicSeq = 0xff + oicSeqBeenRead = true + seqMutex.Unlock() + + for _, want := range []uint8{0xff, 0x00, 0x01} { + got := NextToken() + if !bytes.Equal(got, []byte{want}) { + t.Fatalf("NextToken() = % x, want %02x", got, want) + } + } +} + +func TestSeqToToken(t *testing.T) { + for _, seq := range []uint8{0, 1, 0x7f, 0xff} { + if got := SeqToToken(seq); !bytes.Equal(got, []byte{seq}) { + t.Errorf("SeqToToken(%d) = % x", seq, got) + } + } +} + +func TestGetNextId(t *testing.T) { + a := GetNextId() + b := GetNextId() + if b != a+1 { + t.Fatalf("GetNextId() = %d after %d", b, a) + } +} + +func TestCborMapRoundTrip(t *testing.T) { + in := map[string]interface{}{ + "s": "str", + "b": []byte{1, 2}, + "t": true, + } + + b, err := EncodeCborMap(in) + if err != nil { + t.Fatalf("EncodeCborMap(): %v", err) + } + + out, err := DecodeCborMap(b) + if err != nil { + t.Fatalf("DecodeCborMap(): %v", err) + } + if !reflect.DeepEqual(in, out) { + t.Fatalf("round trip = %#v, want %#v", out, in) + } +} + +func TestCborRoundTrip(t *testing.T) { + in := []interface{}{"a", uint64(5), int64(-3)} + + b, err := EncodeCbor(in) + if err != nil { + t.Fatalf("EncodeCbor(): %v", err) + } + + out, err := DecodeCbor(b) + if err != nil { + t.Fatalf("DecodeCbor(): %v", err) + } + if !reflect.DeepEqual(in, out) { + t.Fatalf("round trip = %#v, want %#v", out, in) + } +} + +func TestDecodeCborInvalid(t *testing.T) { + bad := [][]byte{ + // Truncated map. + {0xa1, 0x61}, + // Reserved additional info. + {0x1c}, + } + + for _, b := range bad { + if _, err := DecodeCbor(b); err == nil { + t.Errorf("DecodeCbor(% x): expected error", b) + } + if _, err := DecodeCborMap(b); err == nil { + t.Errorf("DecodeCborMap(% x): expected error", b) + } + } + + // Valid CBOR, but not a map. + if _, err := DecodeCborMap([]byte{0x01}); err == nil { + t.Errorf("DecodeCborMap(int): expected error") + } +} + +func TestStopAndDrainTimer(t *testing.T) { + // Timer still running. + tm := time.NewTimer(time.Hour) + StopAndDrainTimer(tm) + + // Timer already expired but its value not received; must not block. + tm = time.NewTimer(time.Millisecond) + time.Sleep(20 * time.Millisecond) + + done := make(chan struct{}) + go func() { + StopAndDrainTimer(tm) + close(done) + }() + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatalf("StopAndDrainTimer blocked") + } +}
