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  }