git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/toml/error_test.go (about) 1 //go:build go1.16 2 // +build go1.16 3 4 package toml_test 5 6 import ( 7 "errors" 8 "fmt" 9 "io/fs" 10 "math" 11 "strings" 12 "testing" 13 "time" 14 15 "git.sr.ht/~pingoo/stdx/toml" 16 tomltest "git.sr.ht/~pingoo/stdx/toml/internal/toml-test" 17 ) 18 19 func TestErrorPosition(t *testing.T) { 20 // Note: take care to use leading spaces (not tabs). 21 tests := []struct { 22 test, err string 23 }{ 24 {"array/missing-separator.toml", ` 25 toml: error: expected a comma (',') or array terminator (']'), but got '2' 26 27 At line 1, column 13: 28 29 1 | wrong = [ 1 2 3 ] 30 ^`}, 31 32 {"array/no-close-2.toml", ` 33 toml: error: expected a comma (',') or array terminator (']'), but got end of file 34 35 At line 1, column 10: 36 37 1 | x = [42 # 38 ^`}, 39 40 {"array/tables-2.toml", ` 41 toml: error: Key 'fruit.variety' has already been defined. 42 43 At line 9, column 3-8: 44 45 7 | 46 8 | # This table conflicts with the previous table 47 9 | [fruit.variety] 48 ^^^^^`}, 49 {"datetime/trailing-t.toml", ` 50 toml: error: Invalid TOML Datetime: "2006-01-30T". 51 52 At line 2, column 4-15: 53 54 1 | # Date cannot end with trailing T 55 2 | d = 2006-01-30T 56 ^^^^^^^^^^^`}, 57 } 58 59 fsys := tomltest.EmbeddedTests() 60 for _, tt := range tests { 61 t.Run(tt.test, func(t *testing.T) { 62 input, err := fs.ReadFile(fsys, "invalid/"+tt.test) 63 if err != nil { 64 t.Fatal(err) 65 } 66 67 var x interface{} 68 _, err = toml.Decode(string(input), &x) 69 if err == nil { 70 t.Fatal("err is nil") 71 } 72 73 var pErr toml.ParseError 74 if !errors.As(err, &pErr) { 75 t.Errorf("err is not a ParseError: %T %[1]v", err) 76 } 77 78 tt.err = tt.err[1:] + "\n" // Remove first newline, and add trailing. 79 want := pErr.ErrorWithUsage() 80 81 if !strings.Contains(want, tt.err) { 82 t.Fatalf("\nwant:\n%s\nhave:\n%s", tt.err, want) 83 } 84 }) 85 } 86 } 87 88 func TestParseError(t *testing.T) { 89 tests := []struct { 90 in interface{} 91 toml, err string 92 }{ 93 { 94 &struct{ Int int8 }{}, 95 "Int = 200", 96 `| toml: error: 200 is out of range for int8 97 | 98 | At line 1, column 6-9: 99 | 100 | 1 | Int = 200 101 | ^^^ 102 | Error help: 103 | 104 | This number is too large; this may be an error in the TOML, but it can also be a 105 | bug in the program that uses too small of an integer. 106 | 107 | The maximum and minimum values are: 108 | 109 | size │ lowest │ highest 110 | ───────┼────────────────┼────────── 111 | int8 │ -128 │ 127 112 | int16 │ -32,768 │ 32,767 113 | int32 │ -2,147,483,648 │ 2,147,483,647 114 | int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷ 115 | uint8 │ 0 │ 255 116 | uint16 │ 0 │ 65535 117 | uint32 │ 0 │ 4294967295 118 | uint64 │ 0 │ 1.8 × 10¹⁸ 119 | 120 | int refers to int32 on 32-bit systems and int64 on 64-bit systems. 121 `, 122 }, 123 { 124 &struct{ Int int }{}, 125 fmt.Sprintf("Int = %d", uint64(math.MaxInt64+1)), 126 `| toml: error: 9223372036854775808 is out of range for int64 127 | 128 | At line 1, column 6-25: 129 | 130 | 1 | Int = 9223372036854775808 131 | ^^^^^^^^^^^^^^^^^^^ 132 | Error help: 133 | 134 | This number is too large; this may be an error in the TOML, but it can also be a 135 | bug in the program that uses too small of an integer. 136 | 137 | The maximum and minimum values are: 138 | 139 | size │ lowest │ highest 140 | ───────┼────────────────┼────────── 141 | int8 │ -128 │ 127 142 | int16 │ -32,768 │ 32,767 143 | int32 │ -2,147,483,648 │ 2,147,483,647 144 | int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷ 145 | uint8 │ 0 │ 255 146 | uint16 │ 0 │ 65535 147 | uint32 │ 0 │ 4294967295 148 | uint64 │ 0 │ 1.8 × 10¹⁸ 149 | 150 | int refers to int32 on 32-bit systems and int64 on 64-bit systems. 151 `, 152 }, 153 { 154 &struct{ Float float32 }{}, 155 "Float = 1.1e99", 156 ` 157 | toml: error: 1.1e+99 is out of range for float32 158 | 159 | At line 1, column 8-14: 160 | 161 | 1 | Float = 1.1e99 162 | ^^^^^^ 163 | Error help: 164 | 165 | This number is too large; this may be an error in the TOML, but it can also be a 166 | bug in the program that uses too small of an integer. 167 | 168 | The maximum and minimum values are: 169 | 170 | size │ lowest │ highest 171 | ───────┼────────────────┼────────── 172 | int8 │ -128 │ 127 173 | int16 │ -32,768 │ 32,767 174 | int32 │ -2,147,483,648 │ 2,147,483,647 175 | int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷ 176 | uint8 │ 0 │ 255 177 | uint16 │ 0 │ 65535 178 | uint32 │ 0 │ 4294967295 179 | uint64 │ 0 │ 1.8 × 10¹⁸ 180 | 181 | int refers to int32 on 32-bit systems and int64 on 64-bit systems. 182 `, 183 }, 184 185 { 186 &struct{ D time.Duration }{}, 187 `D = "99 bottles"`, 188 ` 189 | toml: error: invalid duration: "99 bottles" 190 | 191 | At line 1, column 5-15: 192 | 193 | 1 | D = "99 bottles" 194 | ^^^^^^^^^^ 195 | Error help: 196 | 197 | A duration must be as "number<unit>", without any spaces. Valid units are: 198 | 199 | ns nanoseconds (billionth of a second) 200 | us, µs microseconds (millionth of a second) 201 | ms milliseconds (thousands of a second) 202 | s seconds 203 | m minutes 204 | h hours 205 | 206 | You can combine multiple units; for example "5m10s" for 5 minutes and 10 207 | seconds. 208 `, 209 }, 210 } 211 212 prep := func(s string) string { 213 lines := strings.Split(strings.TrimSpace(s), "\n") 214 for i := range lines { 215 if j := strings.IndexByte(lines[i], '|'); j >= 0 { 216 lines[i] = lines[i][j+1:] 217 lines[i] = strings.Replace(lines[i], " ", "", 1) 218 } 219 } 220 return strings.Join(lines, "\n") + "\n" 221 } 222 223 for _, tt := range tests { 224 t.Run("", func(t *testing.T) { 225 _, err := toml.Decode(tt.toml, tt.in) 226 if err == nil { 227 t.Fatalf("err is nil; decoded: %#v", tt.in) 228 } 229 230 var pErr toml.ParseError 231 if !errors.As(err, &pErr) { 232 t.Fatalf("err is not a ParseError: %#v", err) 233 } 234 235 tt.err = prep(tt.err) 236 have := pErr.ErrorWithUsage() 237 238 // have = strings.ReplaceAll(have, " ", "·") 239 // tt.err = strings.ReplaceAll(tt.err, " ", "·") 240 if have != tt.err { 241 t.Fatalf("\nwant:\n%s\nhave:\n%s", tt.err, have) 242 } 243 }) 244 } 245 }