Various improvements and changed encoding/decoding format to LLL-NNN-LLC with reduced character set.
All checks were successful
CI / test (push) Successful in 16s
All checks were successful
CI / test (push) Successful in 16s
This commit is contained in:
@@ -11,19 +11,19 @@ import (
|
||||
"git.hoiting.org/micha/triplex/serial"
|
||||
)
|
||||
|
||||
// Property: Decode(Encode(code)) == code (for valid codes)
|
||||
// Property: Encode(Decode(code)) == code (for valid codes)
|
||||
func TestEncodeDecodeRoundtrip(t *testing.T) {
|
||||
f := func(idx uint32) bool {
|
||||
f := func(idx uint) bool {
|
||||
if idx >= serial.MaxIndex {
|
||||
return true
|
||||
}
|
||||
|
||||
code, err := serial.Decode(idx)
|
||||
code, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
idx2, err := serial.Encode(code)
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -32,28 +32,28 @@ func TestEncodeDecodeRoundtrip(t *testing.T) {
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatalf("Encode/Decode roundtrip failed: %v", err)
|
||||
t.Fatalf("Decode/Encode roundtrip failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Property: Decode(Encode(Decode(i))) == Decode(i) (idempotent on valid indices)
|
||||
// Property: Encode(Decode(Encode(i))) == Encode(i) (idempotent on valid indices)
|
||||
func TestDecodeEncodeDecodeIdempotent(t *testing.T) {
|
||||
f := func(idx uint32) bool {
|
||||
f := func(idx uint) bool {
|
||||
if idx >= serial.MaxIndex {
|
||||
return true
|
||||
}
|
||||
|
||||
code1, err := serial.Decode(idx)
|
||||
code1, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
return true // decode may reject indices (e.g. forbidden pair)
|
||||
}
|
||||
|
||||
idx2, err := serial.Encode(code1)
|
||||
idx2, err := serial.Decode(code1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
code2, err := serial.Decode(idx2)
|
||||
code2, err := serial.Encode(idx2)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -62,18 +62,18 @@ func TestDecodeEncodeDecodeIdempotent(t *testing.T) {
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatalf("Decode/Encode/Decode idempotence failed: %v", err)
|
||||
t.Fatalf("Encode/Decode/Encode idempotence failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Property: forbidden first three letters are never returned by Decode
|
||||
// Property: forbidden first three letters are never returned by Encode
|
||||
func TestForbiddenTripletsNeverDecoded(t *testing.T) {
|
||||
f := func(idx uint32) bool {
|
||||
f := func(idx uint) bool {
|
||||
if idx >= serial.MaxIndex {
|
||||
return true
|
||||
}
|
||||
|
||||
code, err := serial.Decode(idx)
|
||||
code, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
return true // decode may reject indices
|
||||
}
|
||||
@@ -86,31 +86,31 @@ func TestForbiddenTripletsNeverDecoded(t *testing.T) {
|
||||
}
|
||||
|
||||
if err := quick.Check(f, nil); err != nil {
|
||||
t.Fatalf("Forbidden triplets appeared in Decode: %v", err)
|
||||
t.Fatalf("Forbidden triplets appeared in Encode: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: basic check for format LLL-NNN-LC
|
||||
// Optional: basic check for format LLL-NNN-LLC
|
||||
func TestDecodedFormatLooksCorrect(t *testing.T) {
|
||||
f := func(idx uint32) bool {
|
||||
f := func(idx uint) bool {
|
||||
if idx >= serial.MaxIndex {
|
||||
return true
|
||||
}
|
||||
|
||||
code, err := serial.Decode(idx)
|
||||
code, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(code) != 10 {
|
||||
if len(code) != 11 {
|
||||
return false
|
||||
}
|
||||
if code[3] != '-' || code[7] != '-' {
|
||||
return false
|
||||
}
|
||||
|
||||
// letters at positions 0,1,2,8,9
|
||||
for _, p := range []int{0, 1, 2, 8, 9} {
|
||||
// letters at positions 0,1,2,8,9,10
|
||||
for _, p := range []int{0, 1, 2, 8, 9, 10} {
|
||||
if code[p] < 'A' || code[p] > 'Z' {
|
||||
return false
|
||||
}
|
||||
@@ -121,6 +121,9 @@ func TestDecodedFormatLooksCorrect(t *testing.T) {
|
||||
if code[p] < '0' || code[p] > '9' {
|
||||
return false
|
||||
}
|
||||
if code[0] == '0' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -131,18 +134,249 @@ func TestDecodedFormatLooksCorrect(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeRoundtripSpecificIndex11223344(t *testing.T) {
|
||||
baseIdx := uint32(0x11223344)
|
||||
idx := baseIdx % serial.MaxIndex
|
||||
func TestEncodeSpecificIndex0(t *testing.T) {
|
||||
code := "AAA-100-AA"
|
||||
idx := uint(0)
|
||||
|
||||
code, err := serial.Decode(idx)
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for idx %d (derived from 0x11223344): %v", idx, err)
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
idx2, err := serial.Encode(code)
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSpecificIndex1(t *testing.T) {
|
||||
code := "AAA-100-AB"
|
||||
idx := uint(1)
|
||||
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for code %q: %v", code, err)
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
func TestEncodeSpecificIndex20(t *testing.T) {
|
||||
code := "AAA-100-AZ"
|
||||
idx := uint(20)
|
||||
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSpecificIndex21(t *testing.T) {
|
||||
code := "AAA-100-BA"
|
||||
idx := uint(21)
|
||||
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSpecificIndex42(t *testing.T) {
|
||||
code := "AAA-100-CA"
|
||||
idx := uint(42)
|
||||
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSpecificIndex441(t *testing.T) {
|
||||
code := "AAA-101-AA"
|
||||
idx := uint(441)
|
||||
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSpecificIndex396900(t *testing.T) {
|
||||
code := "AAB-100-AA"
|
||||
idx := uint(396900)
|
||||
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSpecificIndex8334900(t *testing.T) {
|
||||
code := "ABA-100-AA"
|
||||
idx := uint(8334900)
|
||||
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSpecificIndex175032900(t *testing.T) {
|
||||
code := "BAA-100-AA"
|
||||
idx := uint(175032900)
|
||||
|
||||
code2, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
// Check excluding the checksum letter
|
||||
if code != code2[0:10] {
|
||||
t.Fatalf("Encode failed for idx %d: exptect: %v actual: %v", idx, code, code2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDencodeSpecificIndex0(t *testing.T) {
|
||||
code := "AAA-100-AAA"
|
||||
idx := uint(0)
|
||||
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx != idx2 {
|
||||
t.Fatalf("roundtrip mismatch: got %d, want %d", idx2, idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDencodeSpecificIndex21(t *testing.T) {
|
||||
code := "AAA-100-BAV"
|
||||
idx := uint(21)
|
||||
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx != idx2 {
|
||||
t.Fatalf("roundtrip mismatch: got %d, want %d", idx2, idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDencodeSpecificIndex1(t *testing.T) {
|
||||
code := "AAA-100-ABB"
|
||||
idx := uint(1)
|
||||
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx != idx2 {
|
||||
t.Fatalf("roundtrip mismatch: got %d, want %d", idx2, idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDencodeSpecificIndex441(t *testing.T) {
|
||||
code := "AAA-101-AAZ"
|
||||
idx := uint(441)
|
||||
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx != idx2 {
|
||||
t.Fatalf("roundtrip mismatch: got %d, want %d", idx2, idx)
|
||||
}
|
||||
}
|
||||
func TestDencodeSpecificIndex396900(t *testing.T) {
|
||||
code := "AAB-100-AAK"
|
||||
idx := uint(396900)
|
||||
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx != idx2 {
|
||||
t.Fatalf("roundtrip mismatch: got %d, want %d", idx2, idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDencodeSpecificIndex8334900(t *testing.T) {
|
||||
code := "ABA-100-AAC"
|
||||
idx := uint(8334900)
|
||||
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx != idx2 {
|
||||
t.Fatalf("roundtrip mismatch: got %d, want %d", idx2, idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDencodeSpecificIndex175032900(t *testing.T) {
|
||||
code := "BAA-100-AAQ"
|
||||
idx := uint(175032900)
|
||||
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx != idx2 {
|
||||
t.Fatalf("roundtrip mismatch: got %d, want %d", idx2, idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeRoundtripSpecificIndex11223344(t *testing.T) {
|
||||
baseIdx := uint(0x11223344)
|
||||
idx := baseIdx % serial.MaxIndex
|
||||
|
||||
code, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for idx %d (derived from 0x11223344): %v", idx, err)
|
||||
}
|
||||
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx2 != idx {
|
||||
@@ -151,51 +385,51 @@ func TestEncodeDecodeRoundtripSpecificIndex11223344(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeOutOfRangeAtMaxIndex(t *testing.T) {
|
||||
_, err := serial.Decode(serial.MaxIndex)
|
||||
_, err := serial.Encode(serial.MaxIndex)
|
||||
if !errors.Is(err, serial.ErrIndexOutOfRange) {
|
||||
t.Fatalf("expected index out of range error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeOutOfRangeAtMaxIndexPlusHundred(t *testing.T) {
|
||||
_, err := serial.Decode(serial.MaxIndex + 100)
|
||||
_, err := serial.Encode(serial.MaxIndex + 100)
|
||||
if !errors.Is(err, serial.ErrIndexOutOfRange) {
|
||||
t.Fatalf("expected index out of range error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeRejectsOldFormatWithThirdDash(t *testing.T) {
|
||||
_, err := serial.Encode("AA-100-AA")
|
||||
_, err := serial.Decode("AA-100-AAA")
|
||||
if !errors.Is(err, serial.ErrInvalidFormat) {
|
||||
t.Fatalf("expected invalid format error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeRejectsLowercaseLetters(t *testing.T) {
|
||||
_, err := serial.Encode("AAa-100-AA")
|
||||
if !errors.Is(err, serial.ErrInvalidLetters) {
|
||||
_, err := serial.Decode("AAa-100-AAA")
|
||||
if !errors.Is(err, serial.ErrInvalidLetter) {
|
||||
t.Fatalf("expected invalid letters error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeRejectsInvalidChecksum(t *testing.T) {
|
||||
_, err := serial.Encode("AAA-100-AB")
|
||||
_, err := serial.Decode("AAA-100-AAB")
|
||||
if !errors.Is(err, serial.ErrInvalidChecksum) {
|
||||
t.Fatalf("expected invalid checksum error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeRejectsNonDigitInNumber(t *testing.T) {
|
||||
_, err := serial.Encode("AAA-1A0-AA")
|
||||
_, err := serial.Decode("AAA-1A0-AAA")
|
||||
if !errors.Is(err, serial.ErrInvalidNumber) {
|
||||
t.Fatalf("expected invalid number error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteCodeRoundtrip(t *testing.T) {
|
||||
original, err := serial.Decode(123456)
|
||||
original, err := serial.Encode(123456)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed: %v", err)
|
||||
t.Fatalf("Encode failed: %v", err)
|
||||
}
|
||||
|
||||
withoutChecksum := original[:len(original)-1]
|
||||
@@ -208,9 +442,9 @@ func TestCompleteCodeRoundtrip(t *testing.T) {
|
||||
t.Fatalf("completed code mismatch: got %q, want %q", full, original)
|
||||
}
|
||||
|
||||
idx2, err := serial.Encode(full)
|
||||
idx2, err := serial.Decode(full)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for completed code %q: %v", full, err)
|
||||
t.Fatalf("Decode failed for completed code %q: %v", full, err)
|
||||
}
|
||||
if idx2 != idx {
|
||||
t.Fatalf("idx mismatch: got %d, want %d", idx, idx2)
|
||||
@@ -218,7 +452,7 @@ func TestCompleteCodeRoundtrip(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCompleteCodeRejectsInvalidFormat(t *testing.T) {
|
||||
_, _, err := serial.CompleteCode("AAA-10-A")
|
||||
_, _, err := serial.CompleteCode("AAA-10-AA")
|
||||
if !errors.Is(err, serial.ErrInvalidFormatNoChecksum) {
|
||||
t.Fatalf("expected invalid no-checksum format error, got: %v", err)
|
||||
}
|
||||
@@ -231,18 +465,18 @@ func TestRandomCodeRoundtrip(t *testing.T) {
|
||||
t.Fatalf("RandomCode failed: %v", err)
|
||||
}
|
||||
|
||||
idx2, err := serial.Encode(code)
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for random code %q: %v", code, err)
|
||||
t.Fatalf("Decode failed for random code %q: %v", code, err)
|
||||
}
|
||||
|
||||
if idx2 != idx {
|
||||
t.Fatalf("returned idx mismatch: got %d, want %d", idx, idx2)
|
||||
}
|
||||
|
||||
decoded, err := serial.Decode(idx)
|
||||
decoded, err := serial.Encode(idx)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode failed for idx %d: %v", idx, err)
|
||||
t.Fatalf("Encode failed for idx %d: %v", idx, err)
|
||||
}
|
||||
|
||||
if decoded != code {
|
||||
@@ -254,7 +488,7 @@ func TestRandomCodeRoundtrip(t *testing.T) {
|
||||
func TestRandomCodeInvokesInUseCallback(t *testing.T) {
|
||||
called := false
|
||||
|
||||
code, idx, err := serial.RandomCode(func(candidate uint32) bool {
|
||||
code, idx, err := serial.RandomCode(func(candidate uint) bool {
|
||||
called = true
|
||||
return false
|
||||
})
|
||||
@@ -265,9 +499,9 @@ func TestRandomCodeInvokesInUseCallback(t *testing.T) {
|
||||
t.Fatalf("expected in-use callback to be called")
|
||||
}
|
||||
|
||||
idx2, err := serial.Encode(code)
|
||||
idx2, err := serial.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode failed for random code %q: %v", code, err)
|
||||
t.Fatalf("Decode failed for random code %q: %v", code, err)
|
||||
}
|
||||
if idx2 != idx {
|
||||
t.Fatalf("returned idx mismatch: got %d, want %d", idx, idx2)
|
||||
@@ -275,7 +509,7 @@ func TestRandomCodeInvokesInUseCallback(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRandomCodeFailsWhenAllIndicesInUse(t *testing.T) {
|
||||
_, _, err := serial.RandomCode(func(candidate uint32) bool {
|
||||
_, _, err := serial.RandomCode(func(candidate uint) bool {
|
||||
return true
|
||||
})
|
||||
if !errors.Is(err, serial.ErrNoAvailableIndex) {
|
||||
@@ -286,7 +520,7 @@ func TestRandomCodeFailsWhenAllIndicesInUse(t *testing.T) {
|
||||
func TestRandomCodeWithOptionsMaxAttempts(t *testing.T) {
|
||||
_, _, err := serial.RandomCodeWithOptions(serial.RandomCodeOptions{
|
||||
MaxAttempts: 1,
|
||||
IsInUse: func(candidate uint32) bool {
|
||||
IsInUse: func(candidate uint) bool {
|
||||
return true
|
||||
},
|
||||
})
|
||||
@@ -297,7 +531,7 @@ func TestRandomCodeWithOptionsMaxAttempts(t *testing.T) {
|
||||
|
||||
func TestRandomCodeWithOptionsRandomSourceFailure(t *testing.T) {
|
||||
_, _, err := serial.RandomCodeWithOptions(serial.RandomCodeOptions{
|
||||
RandomIndex: func(max uint32) (uint32, error) {
|
||||
RandomIndex: func(max uint) (uint, error) {
|
||||
return 0, fmt.Errorf("rng down")
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user