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 9620557f94a847ce5c9c4ccca5260c7c944798f4 Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 00:25:47 2026 +0200 newtmgr/bll: Add unit tests for MTU exchange and UUID helpers Test exchangeMtu() with a fake ble.Client: the reported MTU is used as-is, a bogus value below the ATT default is replaced by 185, the -m override takes precedence and is range checked, and client errors are propagated. The macOS retry path (which sleeps) is skipped on darwin. Also test UuidFromBllUuid() for 16-bit, 128-bit and invalid UUIDs, cross-checked against bledefs.ParseUuid(), and findChr() lookups in a ble.Profile literal. Assisted-by: Claude:claude-opus-5-5 --- newtmgr/bll/bll_test.go | 239 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/newtmgr/bll/bll_test.go b/newtmgr/bll/bll_test.go new file mode 100644 index 0000000..432eae6 --- /dev/null +++ b/newtmgr/bll/bll_test.go @@ -0,0 +1,239 @@ +//go:build !windows + +/** + * 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 bll + +import ( + "errors" + "runtime" + "testing" + + "github.com/JuulLabs-OSS/ble" + + "mynewt.apache.org/newtmgr/newtmgr/nmutil" + "mynewt.apache.org/newtmgr/nmxact/bledefs" +) + +// fakeClient is a ble.Client whose ExchangeMTU reports a fixed MTU. Any +// other method panics (nil embedded interface). +type fakeClient struct { + ble.Client + mtu int + err error + calls []int +} + +func (c *fakeClient) ExchangeMTU(rxMTU int) (int, error) { + c.calls = append(c.calls, rxMTU) + return c.mtu, c.err +} + +func setMtuOverride(t *testing.T, mtu int) { + t.Helper() + old := nmutil.MtuOverride + nmutil.MtuOverride = mtu + t.Cleanup(func() { nmutil.MtuOverride = old }) +} + +func TestExchangeMtu(t *testing.T) { + tests := []struct { + name string + override int + reported int + want uint16 + wantErr string + }{ + {"reported", 0, 247, 247, ""}, + {"reported default", 0, 23, 23, ""}, + {"too small clamped", 0, 17, 185, ""}, + {"override", 100, 247, 100, ""}, + {"override small reported", 100, 17, 100, ""}, + {"override minimum", 23, 247, 23, ""}, + {"override too small", 20, 247, 0, "MTU should be at least 23"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if runtime.GOOS == "darwin" && + tt.reported <= bledefs.BLE_ATT_MTU_DFLT { + // macOS retries with one second sleeps. + t.Skip("skipping macOS MTU retry path") + } + setMtuOverride(t, tt.override) + + cln := &fakeClient{mtu: tt.reported} + mtu, err := exchangeMtu(cln, 512) + if tt.wantErr != "" { + if err == nil || err.Error() != tt.wantErr { + t.Fatalf("err = %v, want %q", err, tt.wantErr) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if mtu != tt.want { + t.Errorf("mtu = %d, want %d", mtu, tt.want) + } + if len(cln.calls) != 1 || cln.calls[0] != 512 { + t.Errorf("ExchangeMTU calls = %v, want [512]", cln.calls) + } + }) + } +} + +func TestExchangeMtuError(t *testing.T) { + setMtuOverride(t, 100) + + want := errors.New("exchange failed") + cln := &fakeClient{mtu: 247, err: want} + mtu, err := exchangeMtu(cln, 256) + if err != want { + t.Errorf("err = %v, want %v", err, want) + } + if mtu != 0 { + t.Errorf("mtu = %d, want 0", mtu) + } + if len(cln.calls) != 1 { + t.Errorf("ExchangeMTU called %d times, want 1", len(cln.calls)) + } +} + +const ( + testSvcUuid = "8d53dc1d-1db7-4cd3-868b-8a527460aa84" + testChrUuid = "da2e7828-fbce-4e01-ae9e-261174997c48" +) + +func mustParseUuid(t *testing.T, s string) bledefs.BleUuid { + t.Helper() + u, err := bledefs.ParseUuid(s) + if err != nil { + t.Fatalf("ParseUuid(%q): %v", s, err) + } + return u +} + +func TestUuidFromBllUuid(t *testing.T) { + u, err := UuidFromBllUuid(ble.UUID16(0x180a)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if u != (bledefs.BleUuid{U16: 0x180a}) { + t.Errorf("16-bit: got %+v", u) + } + + for _, s := range []string{testSvcUuid, testChrUuid} { + u, err := UuidFromBllUuid(ble.MustParse(s)) + if err != nil { + t.Fatalf("%s: unexpected error: %v", s, err) + } + if want := mustParseUuid(t, s); u != want { + t.Errorf("%s: got %+v, want %+v", s, u, want) + } + } + + for _, b := range []ble.UUID{nil, {1}, {1, 2, 3, 4}} { + if _, err := UuidFromBllUuid(b); err == nil { + t.Errorf("%#v: expected error", b) + } + } +} + +func TestFindChr(t *testing.T) { + smpChr := &ble.Characteristic{UUID: ble.MustParse(testChrUuid)} + otherChr := &ble.Characteristic{UUID: ble.UUID16(0x2a29)} + + profile := &ble.Profile{ + Services: []*ble.Service{ + { + UUID: ble.UUID16(0x180a), + Characteristics: []*ble.Characteristic{otherChr}, + }, + { + UUID: ble.MustParse(testSvcUuid), + Characteristics: []*ble.Characteristic{ + {UUID: ble.UUID16(0x2a00)}, + smpChr, + }, + }, + }, + } + + svc := mustParseUuid(t, testSvcUuid) + chr := mustParseUuid(t, testChrUuid) + + tests := []struct { + name string + id bledefs.BleChrId + want *ble.Characteristic + }{ + {"128-bit", bledefs.BleChrId{SvcUuid: svc, ChrUuid: chr}, smpChr}, + {"16-bit", bledefs.BleChrId{ + SvcUuid: bledefs.BleUuid{U16: 0x180a}, + ChrUuid: bledefs.BleUuid{U16: 0x2a29}, + }, otherChr}, + {"chr in other service", bledefs.BleChrId{ + SvcUuid: bledefs.BleUuid{U16: 0x180a}, + ChrUuid: chr, + }, nil}, + {"unknown service", bledefs.BleChrId{ + SvcUuid: bledefs.BleUuid{U16: 0x1800}, + ChrUuid: bledefs.BleUuid{U16: 0x2a00}, + }, nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + id := tt.id + c, err := findChr(profile, &id) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if c != tt.want { + t.Errorf("got %+v, want %+v", c, tt.want) + } + }) + } +} + +func TestFindChrErrors(t *testing.T) { + if _, err := findChr(&ble.Profile{}, nil); err == nil { + t.Errorf("nil chrId: expected error") + } + + bad := &ble.Profile{ + Services: []*ble.Service{{UUID: ble.UUID{1, 2, 3}}}, + } + id := &bledefs.BleChrId{SvcUuid: bledefs.BleUuid{U16: 0x180a}} + if _, err := findChr(bad, id); err == nil { + t.Errorf("invalid service UUID: expected error") + } + + badChr := &ble.Profile{ + Services: []*ble.Service{{ + UUID: ble.UUID16(0x180a), + Characteristics: []*ble.Characteristic{{UUID: ble.UUID{1}}}, + }}, + } + if _, err := findChr(badChr, id); err == nil { + t.Errorf("invalid characteristic UUID: expected error") + } +}
