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 bdefc99e18ce0619bea5e2784b3120f5a422047f Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 00:27:02 2026 +0200 nmxact/bledefs: Add unit tests for addresses, UUIDs and enums Cover BLE address parsing, formatting and JSON (including invalid input), 16-bit and 128-bit UUID parsing, formatting and JSON, CompareUuids and CompareChrIds ordering, and a table test over every enum string map checking ToString/FromString/MarshalJSON/ UnmarshalJSON round trips and the handling of unknown values. Assisted-by: Claude:claude-opus-5-5 --- nmxact/bledefs/bledefs_test.go | 499 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 499 insertions(+) diff --git a/nmxact/bledefs/bledefs_test.go b/nmxact/bledefs/bledefs_test.go new file mode 100644 index 0000000..d2b3240 --- /dev/null +++ b/nmxact/bledefs/bledefs_test.go @@ -0,0 +1,499 @@ +/** + * 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 bledefs + +import ( + "encoding/json" + "testing" +) + +func TestParseBleAddr(t *testing.T) { + tests := []struct { + in string + want [6]byte + str string + }{ + {"00:00:00:00:00:00", [6]byte{}, "00:00:00:00:00:00"}, + {"01:23:45:67:89:ab", [6]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}, + "01:23:45:67:89:ab"}, + {"FF:EE:DD:CC:BB:AA", [6]byte{0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa}, + "ff:ee:dd:cc:bb:aa"}, + {"1:2:3:4:5:6", [6]byte{1, 2, 3, 4, 5, 6}, "01:02:03:04:05:06"}, + } + + for _, tt := range tests { + ba, err := ParseBleAddr(tt.in) + if err != nil { + t.Errorf("ParseBleAddr(%q): unexpected error: %v", tt.in, err) + continue + } + if ba.Bytes != tt.want { + t.Errorf("ParseBleAddr(%q) = % x, want % x", tt.in, ba.Bytes, + tt.want) + } + if s := ba.String(); s != tt.str { + t.Errorf("String() = %q, want %q", s, tt.str) + } + + // String output parses back to the same address. + ba2, err := ParseBleAddr(ba.String()) + if err != nil || ba2 != ba { + t.Errorf("round trip of %q: %v, %v", tt.in, ba2, err) + } + } +} + +func TestParseBleAddrInvalid(t *testing.T) { + bad := []string{ + "", + "01:23:45:67:89", + "01:23:45:67:89:ab:cd", + "01-23-45-67-89-ab", + "01:23:45:67:89:xy", + "01:23:45:67:89:100", + "01:23:45:67::ab", + "0x1:23:45:67:89:ab", + } + + for _, s := range bad { + if ba, err := ParseBleAddr(s); err == nil { + t.Errorf("ParseBleAddr(%q) = %v, expected error", s, ba) + } + } +} + +func TestBleAddrJSON(t *testing.T) { + ba, _ := ParseBleAddr("01:02:03:04:05:06") + + b, err := json.Marshal(&ba) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if string(b) != `"01:02:03:04:05:06"` { + t.Fatalf("Marshal = %s", b) + } + + var got BleAddr + if err := json.Unmarshal(b, &got); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if got != ba { + t.Fatalf("Unmarshal = %v, want %v", got, ba) + } + + for _, bad := range []string{`"zz"`, `5`, `null`} { + if err := json.Unmarshal([]byte(bad), &got); err == nil { + t.Errorf("Unmarshal(%s): expected error", bad) + } + } +} + +func TestBleDevString(t *testing.T) { + ba, _ := ParseBleAddr("aa:bb:cc:dd:ee:ff") + d := BleDev{AddrType: BLE_ADDR_TYPE_RANDOM, Addr: ba} + if s := d.String(); s != "random,aa:bb:cc:dd:ee:ff" { + t.Fatalf("String() = %q", s) + } +} + +func TestParseUuid16(t *testing.T) { + tests := []struct { + in string + want BleUuid16 + str string + }{ + {"0x1800", 0x1800, "0x1800"}, + {"0xfe18", 0xfe18, "0xfe18"}, + {"0XFFFF", 0xffff, "0xffff"}, + // Base is inferred from the prefix; no prefix means decimal. + {"10", 10, "0x000a"}, + } + + for _, tt := range tests { + got, err := ParseUuid16(tt.in) + if err != nil { + t.Errorf("ParseUuid16(%q): %v", tt.in, err) + continue + } + if got != tt.want { + t.Errorf("ParseUuid16(%q) = %#x, want %#x", tt.in, got, tt.want) + } + if s := got.String(); s != tt.str { + t.Errorf("String() = %q, want %q", s, tt.str) + } + } + + for _, bad := range []string{"", "0x10000", "-1", "0xzz", + "ade3d529-c784-4f63-a987-eb69f70ee816"} { + + if _, err := ParseUuid16(bad); err == nil { + t.Errorf("ParseUuid16(%q): expected error", bad) + } + } +} + +func TestParseUuid128(t *testing.T) { + s := "ade3d529-c784-4f63-a987-eb69f70ee816" + want := BleUuid128{ + 0xad, 0xe3, 0xd5, 0x29, 0xc7, 0x84, 0x4f, 0x63, + 0xa9, 0x87, 0xeb, 0x69, 0xf7, 0x0e, 0xe8, 0x16, + } + + got, err := ParseUuid128(s) + if err != nil { + t.Fatalf("ParseUuid128: %v", err) + } + if got != want { + t.Fatalf("ParseUuid128 = % x, want % x", got, want) + } + if got.String() != s { + t.Fatalf("String() = %q, want %q", got.String(), s) + } + + // Upper case input is accepted; output is lower case. + got, err = ParseUuid128("ADE3D529-C784-4F63-A987-EB69F70EE816") + if err != nil || got != want { + t.Fatalf("upper case: % x, %v", got, err) + } + + bad := []string{ + "", + "ade3d529c7844f63a987eb69f70ee816", + "ade3d529-c784-4f63-a987-eb69f70ee81", + "ade3d529-c784-4f63-a987-eb69f70ee8166", + "ade3d529_c784-4f63-a987-eb69f70ee816", + "ade3d529-c784-4f63-a987-eb69f70ee8zz", + "ade3d52-9c784-4f63-a987-eb69f70ee816", + } + for _, b := range bad { + if _, err := ParseUuid128(b); err == nil { + t.Errorf("ParseUuid128(%q): expected error", b) + } + } +} + +func TestBleUuid128JSON(t *testing.T) { + u, _ := ParseUuid128("0c08c213-98ed-4e43-a499-7e1137c39567") + + b, err := json.Marshal(&u) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if string(b) != `"0c08c213-98ed-4e43-a499-7e1137c39567"` { + t.Fatalf("Marshal = %s", b) + } + + var got BleUuid128 + if err := json.Unmarshal(b, &got); err != nil || got != u { + t.Fatalf("Unmarshal = % x, %v", got, err) + } + if err := json.Unmarshal([]byte(`"bad"`), &got); err == nil { + t.Fatalf("Unmarshal(bad): expected error") + } +} + +func TestParseUuid(t *testing.T) { + u, err := ParseUuid("0x2902") + if err != nil { + t.Fatalf("ParseUuid: %v", err) + } + if u.U16 != CccdUuid || u.U128 != (BleUuid128{}) { + t.Fatalf("ParseUuid(0x2902) = %+v", u) + } + if u.String() != "0x2902" { + t.Fatalf("String() = %q", u.String()) + } + + u, err = ParseUuid(NmpPlainSvcUuid) + if err != nil { + t.Fatalf("ParseUuid: %v", err) + } + if u.U16 != 0 || u.String() != NmpPlainSvcUuid { + t.Fatalf("ParseUuid(%s) = %+v", NmpPlainSvcUuid, u) + } + + if _, err := ParseUuid("not-a-uuid"); err == nil { + t.Fatalf("expected error") + } + + if n := NewBleUuid16(0x1801); n.U16 != 0x1801 || n.String() != "0x1801" { + t.Fatalf("NewBleUuid16 = %+v", n) + } +} + +func TestBleUuidJSON(t *testing.T) { + u16 := NewBleUuid16(0x2a00) + b, err := json.Marshal(&u16) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if string(b) != "10752" { + t.Fatalf("Marshal(u16) = %s, want 10752", b) + } + + u128, _ := ParseUuid(OmpUnsecSvcUuid) + b, err = json.Marshal(&u128) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if string(b) != `"`+OmpUnsecSvcUuid+`"` { + t.Fatalf("Marshal(u128) = %s", b) + } + + tests := []struct { + in string + want BleUuid + }{ + {"10752", u16}, + {`"0x2a00"`, u16}, + {`"` + OmpUnsecSvcUuid + `"`, u128}, + } + for _, tt := range tests { + var got BleUuid + if err := json.Unmarshal([]byte(tt.in), &got); err != nil { + t.Errorf("Unmarshal(%s): %v", tt.in, err) + continue + } + if CompareUuids(got, tt.want) != 0 { + t.Errorf("Unmarshal(%s) = %s, want %s", tt.in, got.String(), + tt.want.String()) + } + } + + for _, bad := range []string{`"zzz"`, `70000`, `true`} { + var got BleUuid + if err := json.Unmarshal([]byte(bad), &got); err == nil { + t.Errorf("Unmarshal(%s): expected error", bad) + } + } +} + +func TestCompareUuids(t *testing.T) { + a16 := NewBleUuid16(0x1800) + b16 := NewBleUuid16(0x1801) + a128, _ := ParseUuid("00000000-0000-0000-0000-000000000001") + b128, _ := ParseUuid("00000000-0000-0000-0000-000000000002") + + tests := []struct { + a, b BleUuid + want int + }{ + {a16, a16, 0}, + {a16, b16, -1}, + {b16, a16, 1}, + {a128, a128, 0}, + {a128, b128, -1}, + {b128, a128, 1}, + // 16-bit UUIDs sort after 128-bit ones. + {a16, a128, 1}, + {a128, a16, -1}, + } + + for _, tt := range tests { + got := CompareUuids(tt.a, tt.b) + if (got < 0) != (tt.want < 0) || (got > 0) != (tt.want > 0) { + t.Errorf("CompareUuids(%s, %s) = %d, want sign %d", + tt.a.String(), tt.b.String(), got, tt.want) + } + } +} + +func TestCompareChrIds(t *testing.T) { + s1 := NewBleUuid16(1) + s2 := NewBleUuid16(2) + c1 := NewBleUuid16(10) + c2 := NewBleUuid16(20) + + tests := []struct { + a, b BleChrId + want int + }{ + {BleChrId{s1, c1}, BleChrId{s1, c1}, 0}, + {BleChrId{s1, c1}, BleChrId{s1, c2}, -1}, + {BleChrId{s1, c2}, BleChrId{s1, c1}, 1}, + // Service UUID is the primary key. + {BleChrId{s1, c2}, BleChrId{s2, c1}, -1}, + {BleChrId{s2, c1}, BleChrId{s1, c2}, 1}, + } + + for _, tt := range tests { + got := CompareChrIds(tt.a, tt.b) + if (got < 0) != (tt.want < 0) || (got > 0) != (tt.want > 0) { + t.Errorf("CompareChrIds(%s, %s) = %d, want sign %d", + tt.a.String(), tt.b.String(), got, tt.want) + } + } + + id := BleChrId{s1, c2} + if s := id.String(); s != "s=0x0001 c=0x0014" { + t.Errorf("String() = %q", s) + } +} + +// checkEnum verifies the string and JSON conversions of an enum type +// against its string map. +func checkEnum[T ~int](t *testing.T, m map[T]string, + toStr func(T) string, fromStr func(string) (T, error), hasJSON bool) { + + t.Helper() + + if len(m) == 0 { + t.Fatalf("empty string map") + } + + seen := map[string]bool{} + for v, name := range m { + if seen[name] { + t.Errorf("duplicate name %q", name) + } + seen[name] = true + + if got := toStr(v); got != name { + t.Errorf("ToString(%d) = %q, want %q", int(v), got, name) + } + + got, err := fromStr(name) + if err != nil { + t.Errorf("FromString(%q): %v", name, err) + } else if got != v { + t.Errorf("FromString(%q) = %d, want %d", name, int(got), int(v)) + } + + if !hasJSON { + continue + } + + b, err := json.Marshal(v) + if err != nil { + t.Errorf("Marshal(%d): %v", int(v), err) + continue + } + if want, _ := json.Marshal(name); string(b) != string(want) { + t.Errorf("Marshal(%d) = %s, want %s", int(v), b, want) + } + + var u T + if err := json.Unmarshal(b, &u); err != nil { + t.Errorf("Unmarshal(%s): %v", b, err) + } else if u != v { + t.Errorf("Unmarshal(%s) = %d, want %d", b, int(u), int(v)) + } + } + + if got := toStr(T(12345)); got != "???" { + t.Errorf("ToString(unknown) = %q, want ???", got) + } + if _, err := fromStr("no-such-name"); err == nil { + t.Errorf("FromString(unknown): expected error") + } + + if !hasJSON { + return + } + + if b, err := json.Marshal(T(12345)); err != nil || + string(b) != `"???"` { + + t.Errorf("Marshal(unknown) = %s, %v", b, err) + } + + for _, bad := range []string{`"no-such-name"`, `0`, `null`} { + var u T + if err := json.Unmarshal([]byte(bad), &u); err == nil { + t.Errorf("Unmarshal(%s): expected error", bad) + } + } +} + +func TestEnumStrings(t *testing.T) { + t.Run("BleAddrType", func(t *testing.T) { + checkEnum(t, BleAddrTypeStringMap, + BleAddrTypeToString, BleAddrTypeFromString, true) + }) + t.Run("BleScanFilterPolicy", func(t *testing.T) { + checkEnum(t, BleScanFilterPolicyStringMap, + BleScanFilterPolicyToString, BleScanFilterPolicyFromString, true) + }) + t.Run("BleAdvEventType", func(t *testing.T) { + checkEnum(t, BleAdvEventTypeStringMap, + BleAdvEventTypeToString, BleAdvEventTypeFromString, true) + }) + t.Run("BleAdvConnMode", func(t *testing.T) { + checkEnum(t, BleAdvConnModeStringMap, + BleAdvConnModeToString, BleAdvConnModeFromString, true) + }) + t.Run("BleAdvDiscMode", func(t *testing.T) { + checkEnum(t, BleAdvDiscModeStringMap, + BleAdvDiscModeToString, BleAdvDiscModeFromString, true) + }) + t.Run("BleAdvFilterPolicy", func(t *testing.T) { + checkEnum(t, BleAdvFilterPolicyStringMap, + BleAdvFilterPolicyToString, BleAdvFilterPolicyFromString, true) + }) + t.Run("BleGattOp", func(t *testing.T) { + checkEnum(t, BleGattOpStringMap, + BleGattOpToString, BleGattOpFromString, false) + }) + t.Run("BleSvcType", func(t *testing.T) { + checkEnum(t, BleSvcTypeStringMap, + BleSvcTypeToString, BleSvcTypeFromString, true) + }) + t.Run("BleSmAction", func(t *testing.T) { + checkEnum(t, BleSmActionStringMap, + BleSmActionToString, BleSmActionFromString, true) + }) + t.Run("BleSmIoCap", func(t *testing.T) { + checkEnum(t, BleSmIoCapStringMap, + BleSmIoCapToString, BleSmIoCapFromString, true) + }) + t.Run("BleSmKeyDist", func(t *testing.T) { + checkEnum(t, BleSmKeyDistStringMap, + BleSmKeyDistToString, BleSmKeyDistFromString, true) + }) + t.Run("BleSmAuthReq", func(t *testing.T) { + checkEnum(t, BleSmAuthReqStringMap, + BleSmAuthReqToString, BleSmAuthReqFromString, true) + }) +} + +func TestEnumStringMethods(t *testing.T) { + tests := []struct { + s interface{ String() string } + want string + }{ + {BLE_SM_ACTION_NUMCMP, "numcmp"}, + {BLE_SM_IO_CAP_NO_IO, "no_io"}, + {BLE_SM_KEY_DIST_ID, "id"}, + {BleSmAction(99), "???"}, + } + + for _, tt := range tests { + if got := tt.s.String(); got != tt.want { + t.Errorf("String() = %q, want %q", got, tt.want) + } + } + + for v, name := range BleSmAuthReqStringMap { + if got := v.String(); got != name { + t.Errorf("BleSmAuthReq(%d).String() = %q, want %q", + int(v), got, name) + } + } +}
