...

Source file src/github.com/cloudwego/base64x/base64x_test.go

Documentation: github.com/cloudwego/base64x

     1  /*
     2   * Copyright 2024 CloudWeGo Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package base64x
    18  
    19  import (
    20      `crypto/rand`
    21      `encoding/base64`
    22      `io`
    23      `reflect`
    24      `strings`
    25      `testing`
    26      `unsafe`
    27  )
    28  
    29  type TestPair struct {
    30      decoded string
    31      encoded string
    32  }
    33  
    34  type EncodingTest struct {
    35      enc  Encoding            // Encoding to test
    36      conv func(string) string // Reference string converter
    37  }
    38  
    39  var pairs = []TestPair{
    40      // RFC 3548 examples
    41      {"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+"},
    42      {"\x14\xfb\x9c\x03\xd9", "FPucA9k="},
    43      {"\x14\xfb\x9c\x03", "FPucAw=="},
    44  
    45      // RFC 4648 examples
    46      {"", ""},
    47      {"f", "Zg=="},
    48      {"fo", "Zm8="},
    49      {"foo", "Zm9v"},
    50      {"foob", "Zm9vYg=="},
    51      {"fooba", "Zm9vYmE="},
    52      {"foobar", "Zm9vYmFy"},
    53  
    54      // Wikipedia examples
    55      {"sure.", "c3VyZS4="},
    56      {"sure", "c3VyZQ=="},
    57      {"sur", "c3Vy"},
    58      {"su", "c3U="},
    59      {"leasure.", "bGVhc3VyZS4="},
    60      {"easure.", "ZWFzdXJlLg=="},
    61      {"asure.", "YXN1cmUu"},
    62      {"sure.", "c3VyZS4="},
    63  
    64      // Relatively long strings
    65      {
    66          "Twas brillig, and the slithy toves",
    67          "VHdhcyBicmlsbGlnLCBhbmQgdGhlIHNsaXRoeSB0b3Zlcw==",
    68      }, {
    69          "\x9dyH\xd2Y\x9e^e\x9e\xb1\x9a\x9a\x12\xfe\x8a\x07\xc7\x07\xcc\xe8l\x81" +
    70          "\xf2\xd9\xe3\x89\xb5\x98\xee\xbd\x8etQ`2>\\t:_\xd7w\xe6\xb5\x96\xc7\xff\x9c",
    71          "nXlI0lmeXmWesZqaEv6KB8cHzOhsgfLZ44m1mO69jnRRYDI+XHQ6X9d35rWWx/+c",
    72      },
    73  }
    74  
    75  var crlf_pairs = []TestPair{
    76      // RFC 3548 examples
    77      {"\x14\xfb\x9c\x03\xd9\x7e", "FPuc\r\nA9l+"},
    78      {"\x14\xfb\x9c\x03\xd9", "FP\r\r\r\rucA9k="},
    79      {"\x14\xfb\x9c\x03", "\r\nFPucAw=\r=\n"},
    80  
    81      // RFC 4648 examples
    82      {"", "\r"},
    83      {"f", "Zg\r\n=="},
    84      {"fo", "Zm\r\n8="},
    85      {"fooba", "Zm\r\n9vY\r\nmE="},
    86  
    87      // Wikipedia examples
    88      {"su", "c3U\r="},
    89      {"leasure.", "bGVhc3VyZ\nS4="},
    90      {"easure.", "ZW\r\nFzdXJlLg=\r=\r\n"},
    91      {"asure.", "YXN1cmUu"},
    92      {"sure.", "c3VyZ\r\nS4="},
    93  
    94      // Relatively long strings
    95      {
    96          "Twas brillig, and the slithy toves",
    97          "VHdhcyBicmlsbGlnLCBhbmQgdGhlIHNsaXRoeSB0b3Zlcw\r\n==\r\n",
    98      }, {
    99          "\x9dyH\xd2Y\x9e^e\x9e\xb1\x9a\x9a\x12\xfe\x8a\x07\xc7\x07\xcc\xe8l\x81" +
   100          "\xf2\xd9\xe3\x89\xb5\x98\xee\xbd\x8etQ`2>\\t:_\xd7w\xe6\xb5\x96\xc7\xff\x9c",
   101          "nXlI0lmeXmWesZqaEv6KB8cHzOhsg\r\nfLZ44m1mO69jnRRYDI+XH\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nQ6X9d35rWWx/\r\n+c",
   102      },
   103  }
   104  
   105  var json_pairs = []TestPair{
   106      // RFC 3548 examples
   107      {"\x14\xfb\x9c\x03\xd9\x7e", `FPu\rcA9l+\n`},
   108      {"\x14\xfb\x9c\x03\xd9\x7e", `FPuc\u00419l+`},
   109      {"\x14\xfb\x9c\x03\xd9", `FPucA9k\u003d`},
   110      {"\x14\xfb\x9c\x03\xd9", `FPucA\u0039k\u003d`},
   111      {"\x14\xfb\x9c\x03", `FPucAw\u003d\u003d`},
   112  
   113      // RFC 4648 examples
   114      {"", ""},
   115      {"f", "Zg=="},
   116      {"fo", "Zm8="},
   117      {"foo", "Zm9v"},
   118      {"foob", "Zm9vYg=="},
   119      {"fooba", "Zm9vYmE="},
   120      {"foobar", "Zm9vYmFy"},
   121  
   122      // Wikipedia examples
   123      {"sure.", "c3VyZS4="},
   124      {"sure", "c3VyZQ=="},
   125      {"sur", "c3Vy"},
   126      {"su", "c3U="},
   127      {"leasure.", "bGVhc3VyZS4="},
   128      {"easure.", "ZWFzdXJlLg=="},
   129      {"asure.", "YXN1cmUu"},
   130      {"sure.", "c3VyZS4="},
   131  
   132      // Relatively long strings
   133      {
   134          "Twas brillig, and the slithy toves",
   135          "VHdhcyBicmlsbGlnLCBhbmQgdGhlIHNsaXRoeSB0b3Zlcw==",
   136      }, {
   137          "\x9dyH\xd2Y\x9e^e\x9e\xb1\x9a\x9a\x12\xfe\x8a\x07\xc7\x07\xcc\xe8l\x81" +
   138          "\xf2\xd9\xe3\x89\xb5\x98\xee\xbd\x8etQ`2>\\t:_\xd7w\xe6\xb5\x96\xc7\xff\x9c",
   139          `nXlI0lmeXmWesZqaEv6KB8cHzOhsgfLZ44m1mO\u0036\u0039jnRRYDI+XHQ6X9d35rWWx\/+c`,
   140      },
   141  }
   142  
   143  // Do nothing to a reference base64 string (leave in standard format)
   144  func stdRef(ref string) string {
   145      return ref
   146  }
   147  
   148  // Convert a reference string to URL-encoding
   149  func urlRef(ref string) string {
   150      ref = strings.ReplaceAll(ref, "+", "-")
   151      ref = strings.ReplaceAll(ref, "/", "_")
   152      return ref
   153  }
   154  
   155  // Convert a reference string to raw, unpadded format
   156  func rawRef(ref string) string {
   157      return strings.ReplaceAll(ref, "=", "")
   158  }
   159  
   160  // Both URL and unpadding conversions
   161  func rawURLRef(ref string) string {
   162      return rawRef(urlRef(ref))
   163  }
   164  
   165  var encodingTests = []EncodingTest{
   166      {StdEncoding, stdRef},
   167      {URLEncoding, urlRef},
   168      {RawStdEncoding, rawRef},
   169      {RawURLEncoding, rawURLRef},
   170  }
   171  
   172  func testEqual(t *testing.T, msg string, args ...interface{}) bool {
   173      t.Helper()
   174      if args[len(args) - 2] != args[len(args) - 1] {
   175          t.Errorf(msg, args...)
   176          return false
   177      }
   178      return true
   179  }
   180  
   181  func TestEncoderRecover(t *testing.T) {
   182      t.Run("nil dst", func(t *testing.T) {
   183          in := []byte("abc")
   184          defer func(){
   185              if v := recover(); v != nil {
   186                  println("recover:", v)
   187              } else {
   188                  t.Fatal("not recover")
   189              }
   190          }()
   191          b64encode(nil, &in, int(StdEncoding))
   192      })
   193      t.Run("nil src", func(t *testing.T) {
   194          in := []byte("abc")
   195          (*reflect.SliceHeader)(unsafe.Pointer(&in)).Data = uintptr(0)
   196          out := make([]byte, 0, 10)
   197          defer func(){
   198              if v := recover(); v != nil {
   199                  println("recover:", v)
   200              } else {
   201                  t.Fatal("not recover")
   202              }
   203          }()
   204          b64encode(&out, &in, int(StdEncoding))
   205      })
   206  }
   207  
   208  func TestEncoder(t *testing.T) {
   209      for _, p := range pairs {
   210          for _, tt := range encodingTests {
   211              got := tt.enc.EncodeToString([]byte(p.decoded))
   212              testEqual(t, "Encode(%q) = %q, want %q", p.decoded, got, tt.conv(p.encoded))
   213          }
   214      }
   215  }
   216  
   217  func benchmarkStdlibWithSize(b *testing.B, nb int) {
   218      buf := make([]byte, nb)
   219      dst := make([]byte, base64.StdEncoding.EncodedLen(nb))
   220      _, _ = io.ReadFull(rand.Reader, buf)
   221      b.SetBytes(int64(nb))
   222      b.ResetTimer()
   223      b.RunParallel(func(pb *testing.PB) {
   224          for pb.Next() {
   225              base64.StdEncoding.Encode(dst, buf)
   226          }
   227      })
   228  }
   229  
   230  func benchmarkBase64xWithSize(b *testing.B, nb int) {
   231      buf := make([]byte, nb)
   232      dst := make([]byte, StdEncoding.EncodedLen(nb))
   233      _, _ = io.ReadFull(rand.Reader, buf)
   234      b.SetBytes(int64(nb))
   235      b.ResetTimer()
   236      b.RunParallel(func(pb *testing.PB) {
   237          for pb.Next() {
   238              StdEncoding.Encode(dst, buf)
   239          }
   240      })
   241  }
   242  
   243  func BenchmarkEncoderStdlib_16B    (b *testing.B) { benchmarkStdlibWithSize(b, 16) }
   244  func BenchmarkEncoderStdlib_56B    (b *testing.B) { benchmarkStdlibWithSize(b, 56) }
   245  func BenchmarkEncoderStdlib_128B   (b *testing.B) { benchmarkStdlibWithSize(b, 128) }
   246  func BenchmarkEncoderStdlib_4kB    (b *testing.B) { benchmarkStdlibWithSize(b, 4 * 1024) }
   247  func BenchmarkEncoderStdlib_256kB  (b *testing.B) { benchmarkStdlibWithSize(b, 256 * 1024) }
   248  func BenchmarkEncoderStdlib_1MB    (b *testing.B) { benchmarkStdlibWithSize(b, 1024 * 1024) }
   249  
   250  func BenchmarkEncoderBase64x_16B   (b *testing.B) { benchmarkBase64xWithSize(b, 16) }
   251  func BenchmarkEncoderBase64x_56B   (b *testing.B) { benchmarkBase64xWithSize(b, 56) }
   252  func BenchmarkEncoderBase64x_128B  (b *testing.B) { benchmarkBase64xWithSize(b, 128) }
   253  func BenchmarkEncoderBase64x_4kB   (b *testing.B) { benchmarkBase64xWithSize(b, 4 * 1024) }
   254  func BenchmarkEncoderBase64x_256kB (b *testing.B) { benchmarkBase64xWithSize(b, 256 * 1024) }
   255  func BenchmarkEncoderBase64x_1MB   (b *testing.B) { benchmarkBase64xWithSize(b, 1024 * 1024) }
   256  
   257  func TestDecoder(t *testing.T) {
   258      for _, p := range pairs {
   259          for _, tt := range encodingTests {
   260              encoded := tt.conv(p.encoded)
   261              dbuf := make([]byte, tt.enc.DecodedLen(len(encoded)))
   262              count, err := tt.enc.Decode(dbuf, []byte(encoded))
   263              testEqual(t, "Decode(%q) = error %v, want %v", encoded, err, error(nil))
   264              testEqual(t, "Decode(%q) = length %v, want %v", encoded, count, len(p.decoded))
   265              testEqual(t, "Decode(%q) = %q, want %q", encoded, string(dbuf[0:count]), p.decoded)
   266  
   267              dbuf, err = tt.enc.DecodeString(encoded)
   268              testEqual(t, "DecodeString(%q) = error %v, want %v", encoded, err, error(nil))
   269              testEqual(t, "DecodeString(%q) = %q, want %q", encoded, string(dbuf), p.decoded)
   270          }
   271      }
   272  }
   273  
   274  func TestDecoderRecover(t *testing.T) {
   275      t.Run("nil dst", func(t *testing.T) {
   276          in := []byte("abc")
   277          defer func(){
   278              if v := recover(); v != nil {
   279                  println("recover:", v)
   280              } else {
   281                  t.Fatal("not recover")
   282              }
   283          }()
   284          b64decode(nil, unsafe.Pointer(&in[0]), len(in), int(StdEncoding))
   285      })
   286      t.Run("nil src", func(t *testing.T) {
   287          out := make([]byte, 0, 10)
   288          defer func(){
   289              if v := recover(); v != nil {
   290                  println("recover:", v)
   291              } else {
   292                  t.Fatal("not recover")
   293              }
   294          }()
   295          b64decode(&out, nil, 5, int(StdEncoding))
   296      })
   297  }
   298  
   299  func TestDecoderCRLF(t *testing.T) {
   300      for _, p := range crlf_pairs {
   301          for _, tt := range encodingTests {
   302              encoded := tt.conv(p.encoded)
   303              dbuf := make([]byte, tt.enc.DecodedLen(len(encoded)))
   304              count, err := tt.enc.Decode(dbuf, []byte(encoded))
   305              testEqual(t, "Decode(%q) = error %v, want %v", encoded, err, error(nil))
   306              testEqual(t, "Decode(%q) = length %v, want %v", encoded, count, len(p.decoded))
   307              testEqual(t, "Decode(%q) = %q, want %q", encoded, string(dbuf[0:count]), p.decoded)
   308  
   309              dbuf, err = tt.enc.DecodeString(encoded)
   310              testEqual(t, "DecodeString(%q) = error %v, want %v", encoded, err, error(nil))
   311              testEqual(t, "DecodeString(%q) = %q, want %q", encoded, string(dbuf), p.decoded)
   312          }
   313      }
   314  }
   315  
   316  func TestDecoderJSON(t *testing.T) {
   317      for _, p := range json_pairs {
   318          encoded := p.encoded
   319          dbuf := make([]byte, JSONStdEncoding.DecodedLen(len(encoded)))
   320          count, err := JSONStdEncoding.Decode(dbuf, []byte(encoded))
   321          testEqual(t, "Decode(%q) = error %v, want %v", encoded, err, error(nil))
   322          testEqual(t, "Decode(%q) = length %v, want %v", encoded, count, len(p.decoded))
   323          testEqual(t, "Decode(%q) = %q, want %q", encoded, string(dbuf[0:count]), p.decoded)
   324  
   325          dbuf, err = JSONStdEncoding.DecodeString(encoded)
   326          testEqual(t, "DecodeString(%q) = error %v, want %v", encoded, err, error(nil))
   327          testEqual(t, "DecodeString(%q) = %q, want %q", encoded, string(dbuf), p.decoded)
   328      }
   329  }
   330  
   331  func TestDecoderError(t *testing.T) {
   332      _, err := StdEncoding.DecodeString("!aGVsbG8sIHdvcmxk")
   333      if err != base64.CorruptInputError(0) {
   334          panic(err)
   335      }
   336      _, err = StdEncoding.DecodeString("aGVsbG8!sIHdvcmxk")
   337      if err != base64.CorruptInputError(7) {
   338          panic(err)
   339      }
   340      _, err = StdEncoding.DecodeString("123456")
   341      if err != base64.CorruptInputError(6) {
   342          panic(err)
   343      }
   344      _, err = StdEncoding.DecodeString("1234;6")
   345      if err != base64.CorruptInputError(4) {
   346          panic(err)
   347      }
   348      _, err = StdEncoding.DecodeString("F\xaa\xaa\xaa\xaaDDDDDDDDDDDDD//z")
   349      if err != base64.CorruptInputError(1) {
   350          panic(err)
   351      } 
   352  }
   353  
   354  func benchmarkStdlibDecoder(b *testing.B, v string) {
   355      src := []byte(v)
   356      dst := make([]byte, base64.StdEncoding.DecodedLen(len(v)))
   357      b.SetBytes(int64(len(v)))
   358      b.ResetTimer()
   359      b.RunParallel(func(pb *testing.PB) {
   360          for pb.Next() {
   361              _, _ = base64.StdEncoding.Decode(dst, src)
   362          }
   363      })
   364  }
   365  
   366  func benchmarkBase64xDecoder(b *testing.B, v string) {
   367      src := []byte(v)
   368      dst := make([]byte, StdEncoding.DecodedLen(len(v)))
   369      b.SetBytes(int64(len(v)))
   370      b.ResetTimer()
   371      b.RunParallel(func(pb *testing.PB) {
   372          for pb.Next() {
   373              _, _ = StdEncoding.Decode(dst, src)
   374          }
   375      })
   376  }
   377  
   378  var data = `////////////////////////////////////////////////////////////////`
   379  func BenchmarkDecoderStdLib  (b *testing.B) { benchmarkStdlibDecoder(b, data) }
   380  func BenchmarkDecoderBase64x (b *testing.B) { benchmarkBase64xDecoder(b, data) }
   381  

View as plain text