go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/lex64/lex64_test.go (about) 1 // Copyright 2022 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package lex64 16 17 import ( 18 "fmt" 19 "sort" 20 "strings" 21 "testing" 22 "testing/quick" 23 24 "github.com/google/go-cmp/cmp" 25 ) 26 27 // TestAlphabet checks that the alphabet is in lexicographic order. 28 func TestAlphabet(t *testing.T) { 29 t.Parallel() 30 31 as := []string{ 32 alphabetV1, 33 alphabetV2, 34 fmt.Sprintf("-%s", alphabetV1), 35 fmt.Sprintf("-%s", alphabetV2), 36 } 37 38 for _, alphabet := range as { 39 sortedAlphabet := sortString(alphabet) 40 41 if diff := cmp.Diff(sortedAlphabet, alphabet); diff != "" { 42 t.Errorf("unexpected diff (-want +got): %s", diff) 43 } 44 } 45 } 46 47 // TestGetEncoding tests that invalid encoding names produce an error with 48 // the expected message. 49 func TestGetEncoding(t *testing.T) { 50 t.Parallel() 51 52 _, err := GetEncoding(Scheme("17b72b62-202c-4e09-91f1-94bbd1b2ede0")) 53 switch err { 54 case nil: 55 t.Error("error unexpectedly nil") 56 default: 57 expected := fmt.Sprintf("invalid lex64 version %q", "17b72b62-202c-4e09-91f1-94bbd1b2ede0") 58 actual := err.Error() 59 if diff := cmp.Diff(expected, actual); diff != "" { 60 t.Errorf("unexpected diff (-want +got): %s", diff) 61 } 62 } 63 } 64 65 // TestEncodeAndDecode tests that encoding and decoding work and roundtrip as 66 // expected. 67 func TestEncodeAndDecode(t *testing.T) { 68 t.Parallel() 69 70 cases := []struct { 71 name string 72 in string 73 encoding Scheme 74 out string 75 }{ 76 { 77 name: "F1F2F3 v1", 78 in: "\xF1\xF2\xF3", 79 encoding: V1, 80 out: "wUAn", 81 }, 82 { 83 name: "F1F2F3 v2", 84 in: "\xF1\xF2\xF3", 85 encoding: V2, 86 out: "wUAn", 87 }, 88 { 89 name: "empty string v1", 90 in: "", 91 encoding: V1, 92 out: "", 93 }, 94 { 95 name: "empty string v2", 96 in: "", 97 encoding: V1, 98 out: "", 99 }, 100 { 101 name: "single char v1", 102 in: "\x00", 103 encoding: V1, 104 out: "00", 105 }, 106 { 107 name: "single char v2", 108 in: "\x00", 109 encoding: V2, 110 out: "..", 111 }, 112 { 113 name: "random string v1", 114 in: "\x67\x9a\x5c\x48\xbe\x97\x27\x75\xdf\x6a", 115 encoding: V1, 116 out: "OtdRHAuM9rMUPV", 117 }, 118 { 119 name: "random string v2", 120 in: "\x67\x9a\x5c\x48\xbe\x97\x27\x75\xdf\x6a", 121 encoding: V2, 122 out: "OtdRHAuM8rMUPV", 123 }, 124 } 125 126 for _, tt := range cases { 127 tt := tt 128 t.Run(tt.name, func(t *testing.T) { 129 t.Parallel() 130 131 encoding := encodings[tt.encoding] 132 133 in := []byte(tt.in) 134 actual, err := Encode(encoding, in) 135 if err != nil { 136 t.Errorf("unexpected error for subtest %q: %s", tt.name, err) 137 } 138 expected := tt.out 139 140 if diff := cmp.Diff(expected, actual); diff != "" { 141 t.Errorf("unexpected diff during encoding for subtest %q (-want +got): %s", tt.name, diff) 142 } 143 }) 144 } 145 } 146 147 // TestRoundTripEquality tests that encoding and decoding with padding 148 // round-trips. 149 func TestRoundTripEquality(t *testing.T) { 150 t.Parallel() 151 152 for _, v := range []struct { 153 opts Scheme 154 }{ 155 { 156 opts: V1, 157 }, 158 { 159 opts: V1Padding, 160 }, 161 { 162 opts: V2, 163 }, 164 { 165 opts: V2Padding, 166 }, 167 } { 168 roundTrip := func(a []byte) bool { 169 encoding, _ := GetEncoding(v.opts) 170 encoded, err := Encode(encoding, a) 171 if err != nil { 172 panic(err.Error()) 173 } 174 decoded, err := Decode(encoding, encoded) 175 if err != nil { 176 panic(err.Error()) 177 } 178 return cmpBytes(a, decoded) == 0 179 } 180 181 if err := quick.Check(roundTrip, nil); err != nil { 182 t.Errorf("unexpected error: %s", err) 183 } 184 } 185 } 186 187 // TestPreservationOfComparisonOrder tests that encoding with padding preserves 188 // comparison order. 189 func TestPreservationOfComparisonOrder(t *testing.T) { 190 t.Parallel() 191 192 for _, v := range []struct { 193 name string 194 opts Scheme 195 }{ 196 { 197 name: "current", 198 opts: V2, 199 }, 200 { 201 name: "current with padding", 202 opts: V2Padding, 203 }, 204 { 205 name: "legacy", 206 opts: V1, 207 }, 208 { 209 name: "legacy with padding", 210 opts: V1Padding, 211 }, 212 } { 213 v := v 214 t.Run(v.name, func(t *testing.T) { 215 t.Parallel() 216 expected := func(a []byte, b []byte) int { 217 return cmpBytes(a, b) 218 } 219 220 actual := func(a []byte, b []byte) int { 221 encoding, _ := GetEncoding(v.opts) 222 a1, err := Encode(encoding, a) 223 if err != nil { 224 panic(err.Error()) 225 } 226 b1, err := Encode(encoding, b) 227 if err != nil { 228 panic(err.Error()) 229 } 230 return strings.Compare(a1, b1) 231 } 232 if err := quick.CheckEqual(expected, actual, &quick.Config{ 233 MaxCount: 10000, 234 }); err != nil { 235 t.Errorf("unexpected error: %s", err) 236 } 237 }) 238 } 239 } 240 241 // TestConcatenationFails is a demonstration that concatenation is not respected. 242 func TestConcatenationFails(t *testing.T) { 243 t.Parallel() 244 245 encoding, _ := GetEncoding(V2Padding) 246 247 a := []byte("a") 248 b := []byte("b") 249 250 a2, err := Encode(encoding, a) 251 if err != nil { 252 t.Error(err) 253 } 254 255 b2, err := Encode(encoding, b) 256 if err != nil { 257 t.Error(err) 258 } 259 260 _, err = Decode(encoding, fmt.Sprintf("%s%s", a2, b2)) 261 switch err { 262 case nil: 263 t.Error("error is unexpectedly nil") 264 default: 265 if !strings.Contains(err.Error(), "illegal base64") { 266 t.Errorf("wrong error: %s", err) 267 } 268 } 269 } 270 271 // cmpString compares two sequences of bytes and returns +1, 0, or -1. 272 func cmpBytes(a []byte, b []byte) int { 273 return strings.Compare(string(a), string(b)) 274 } 275 276 // sortString sorts a string characterwise. 277 func sortString(s string) string { 278 chars := strings.Split(s, "") 279 sort.Strings(chars) 280 return strings.Join(chars, "") 281 }