go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/convert_to_test.go (about)

     1  // Copyright 2023 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 data
    16  
    17  import (
    18  	"bytes"
    19  	"testing"
    20  
    21  	"golang.org/x/exp/slices"
    22  )
    23  
    24  // okToType checks that a type conversion succeeds.
    25  func okToType[T comparable](t *testing.T, input any, expect T) {
    26  	t.Helper()
    27  	if val, ok := LosslessConvertTo[T](input); !ok || val != expect {
    28  		t.Errorf("%[1]T(%[1]v) - ok=%[2]t, value=%[3]T(%[3]v) | expect=%[4]v", input, ok, val, expect)
    29  	}
    30  }
    31  
    32  // failToType checks that a type conversion fails.
    33  func failToType[T comparable](t *testing.T, input any) {
    34  	t.Helper()
    35  	if val, ok := LosslessConvertTo[T](input); ok {
    36  		t.Errorf("%[1]T(%[1]v) - ok=%[2]t, value=%[3]T(%[3]v)", input, ok, val)
    37  	}
    38  }
    39  
    40  // TestConvertToInt tests conversion to signed integral types.
    41  func TestConvertToInt(t *testing.T) {
    42  	t.Parallel()
    43  
    44  	// The integer 100 can be represented as an int8.
    45  	//
    46  	// However, we want to allow conversions to succeed only when **any** value of the given type would be possible to convert.
    47  	// Only an int8 can cast losslessly to an int8.
    48  	failToType[int8](t, int(100))
    49  	okToType[int8](t, int8(100), 100)
    50  	failToType[int8](t, int16(100))
    51  	failToType[int8](t, int32(100))
    52  	failToType[int8](t, int64(100))
    53  
    54  	// An int8 or an int16 can cast losslessly to an int16
    55  	failToType[int16](t, int(100))
    56  	okToType[int16](t, int8(100), 100)
    57  	okToType[int16](t, int16(100), 100)
    58  	failToType[int16](t, int32(100))
    59  	failToType[int16](t, int64(100))
    60  
    61  	// An int8, int16, or int32 can cast losslessly to an int32.
    62  	failToType[int32](t, int(100))
    63  	okToType[int32](t, int8(100), 100)
    64  	okToType[int32](t, int16(100), 100)
    65  	okToType[int32](t, int32(100), 100)
    66  	failToType[int32](t, int64(100))
    67  
    68  	// An int8, int16, int32, or int64 can cast losslessly to an int64.
    69  	okToType[int64](t, int(100), 100)
    70  	okToType[int64](t, int8(100), 100)
    71  	okToType[int64](t, int16(100), 100)
    72  	okToType[int64](t, int32(100), 100)
    73  	okToType[int64](t, int64(100), 100)
    74  
    75  	// Can convert uint of a smaller bit size
    76  	okToType[int64](t, uint16(100), 100)
    77  
    78  	// all other coversions fail
    79  	failToType[int](t, "no")
    80  	failToType[int](t, nil)
    81  	failToType[int](t, &struct{}{})
    82  }
    83  
    84  // TestConvertToInt tests conversion to unsigned integral types.
    85  func TestConvertToUint(t *testing.T) {
    86  	// only uint8 casts losslessly to uint8
    87  	failToType[uint8](t, uint(100))
    88  	okToType[uint8](t, uint8(100), 100)
    89  	failToType[uint8](t, uint16(100))
    90  	failToType[uint8](t, uint32(100))
    91  	failToType[uint8](t, uint64(100))
    92  
    93  	// only uint8 and uint16 casts losslessly to uint16
    94  	failToType[uint16](t, uint(100))
    95  	okToType[uint16](t, uint8(100), 100)
    96  	okToType[uint16](t, uint16(100), 100)
    97  	failToType[uint16](t, uint32(100))
    98  	failToType[uint16](t, uint64(100))
    99  
   100  	// only uint8, uint16, and uint32 casts losslessly to uint32
   101  	failToType[uint32](t, uint(100))
   102  	okToType[uint32](t, uint8(100), 100)
   103  	okToType[uint32](t, uint16(100), 100)
   104  	okToType[uint32](t, uint32(100), 100)
   105  	failToType[uint32](t, uint64(100))
   106  
   107  	// All unsigned integral types cast losslessly to uint32
   108  	okToType[uint64](t, uint(100), 100)
   109  	okToType[uint64](t, uint8(100), 100)
   110  	okToType[uint64](t, uint16(100), 100)
   111  	okToType[uint64](t, uint32(100), 100)
   112  	okToType[uint64](t, uint64(100), 100)
   113  
   114  	// Can not convert int of any size to uint
   115  	failToType[uint64](t, int8(100))
   116  
   117  	// all other coversions fail
   118  	failToType[uint](t, "no")
   119  	failToType[uint](t, nil)
   120  	failToType[uint](t, &struct{}{})
   121  }
   122  
   123  // TestConvertToFloat tests conversions between float32 and float64.
   124  func TestConvertToFloat(t *testing.T) {
   125  	okToType[float32](t, float32(100.0), 100)
   126  	okToType[float64](t, float32(100.0), 100)
   127  	okToType[float64](t, float64(100.0), 100)
   128  	failToType[float32](t, float64(100.0))
   129  
   130  	// An int8 can be represented losslessly as a float.
   131  	// However, we are going to be conservative and prevent this cast.
   132  	//
   133  	// I hope this decision doesn't come back to bite us.
   134  	failToType[float32](t, int8(100))
   135  
   136  	// All other coversions fail.
   137  	failToType[float32](t, "no")
   138  	failToType[float32](t, nil)
   139  	failToType[float32](t, &struct{}{})
   140  }
   141  
   142  // TestConvertToComplex tests converting numbers to complex numbers.
   143  func TestConvertToComplex(t *testing.T) {
   144  	t.Parallel()
   145  
   146  	// We allow conversions between complex64 and complex128 except for complex128->complex64.
   147  	okToType[complex64](t, complex64(100.0), 100)
   148  	okToType[complex128](t, complex64(100.0), 100)
   149  	okToType[complex128](t, complex128(100.0), 100)
   150  	failToType[complex64](t, complex128(100.0))
   151  
   152  	// All other coversions fail.
   153  	failToType[complex64](t, "no")
   154  	failToType[complex64](t, int8(1))
   155  	failToType[complex64](t, float32(1))
   156  	failToType[complex64](t, nil)
   157  	failToType[complex64](t, &struct{}{})
   158  }
   159  
   160  // TestConvertStrings tests conversion to strings.
   161  func TestConvertStrings(t *testing.T) {
   162  	t.Parallel()
   163  	okToType[string](t, "hello", "hello")
   164  	okToType[string](t, []byte("hello"), "hello")
   165  	okToType[string](t, []rune("hello"), "hello")
   166  
   167  	fail := func(message string, ok bool, val any, input any) {
   168  		t.Errorf("%[1]T(%[1]v) - ok=%[2]t, value=%[3]T(%[3]v) | expect=%[4]v", message, ok, val, input)
   169  	}
   170  
   171  	if val, ok := LosslessConvertTo[[]byte]("hello"); !ok || !bytes.Equal(val, []byte("hello")) {
   172  		fail("hello", ok, val, []byte("hello"))
   173  	}
   174  
   175  	if val, ok := LosslessConvertTo[[]rune]("hello"); !ok || !slices.Equal(val, []rune("hello")) {
   176  		fail("hello", ok, val, []rune("hello"))
   177  	}
   178  
   179  	// reflect.Value.ConvertTo would allow this, but we do not.
   180  	failToType[string](t, 100)
   181  }
   182  
   183  // TestNilConversion tests conversion of nil values.
   184  func TestNilConversion(t *testing.T) {
   185  	t.Parallel()
   186  	okToType[any](t, nil, nil)
   187  	okToType[*struct{}](t, nil, nil)
   188  
   189  	if val, ok := LosslessConvertTo[func()](nil); !ok || val != nil {
   190  		t.Errorf("%[1]T(%[1]v) - ok=%[2]t, value=%[3]T(%[3]v) | expect=%[4]v", "hello", ok, any(val), []byte("hello"))
   191  	}
   192  
   193  }
   194  
   195  // TestInterfaceConversion tests converting a value to an interface that it satisfies.
   196  func TestInterfaceConversion(t *testing.T) {
   197  	t.Parallel()
   198  	okToType[any](t, 100, 100)
   199  }
   200  
   201  // TestDifferentConcreteTypes tests casting between different conrete types with teh same underlying representation.
   202  func TestDifferentConcreteTypes(t *testing.T) {
   203  	t.Parallel()
   204  	type myString string
   205  	okToType[string](t, myString("hello"), "hello")
   206  	okToType[myString](t, "hello", "hello")
   207  
   208  	type myStruct struct{ CoolField int }
   209  	okToType[myStruct](t, struct{ CoolField int }{100}, myStruct{100})
   210  	okToType[myStruct](t, myStruct{100}, myStruct{100})
   211  	okToType[struct{ CoolField int }](t, myStruct{100}, struct{ CoolField int }{100})
   212  
   213  	if val, ok := LosslessConvertTo[*myStruct](&struct{ CoolField int }{100}); !ok || val == nil || val.CoolField != 100 {
   214  		t.Error("failed to losslessly convert &struct{...} to *myStruct")
   215  	}
   216  }